commit 6f6808c9d3da7df3a3243b3707e15af2e48acceb Author: Sergio Durigan Junior Date: Sun Aug 7 23:59:31 2016 -0400 Imported Upstream version 2.3.4 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..623b625 --- /dev/null +++ b/LICENSE @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..f687a3c --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,10 @@ +include LICENSE README.rst requirements.txt UPGRADING.rst +include createdb.py +recursive-include pagure * +recursive-include files * +recursive-include milters * +recursive-include tests * +recursive-include doc * +recursive-include alembic * +recursive-include ev-server * +recursive-include webhook-server * diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..1423d67 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 1.1 +Name: pagure +Version: 2.3.4 +Summary: A light-weight git-centered forge based on pygit2.. +Home-page: https://fedorahosted.org/pagure/ +Author: Pierre-Yves Chibon +Author-email: pingou@pingoured.fr +License: GPLv2+ +Download-URL: https://fedorahosted.org/releases/p/r/pagure/ +Description: UNKNOWN +Platform: UNKNOWN diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..d895e7c --- /dev/null +++ b/README.rst @@ -0,0 +1,81 @@ +Pagure +====== + +:Author: Pierre-Yves Chibon + + +Pagure is a git-centered forge, python based using pygit2. + +With pagure you can host your project with its documentation, let your users +report issues or request enhancements using the ticketing system and build your +community of contributors by allowing them to fork your projects and contribute +to it via the now-popular pull-request mechanism. + + +Homepage: https://pagure.io/pagure + +See it at work: https://pagure.io + + +Playground version: https://stg.pagure.io + + + +Get it running +============== + +* Install the needed system libraries:: + + sudo dnf install git python-virtualenv libgit2-devel \ + libjpeg-devel gcc libffi-devel redhat-rpm-config + + .. note:: Do note the version of libgit2 that you install, for example + in ``libgit2-0.23.4-1`` you need to keep in mind the ``0.23`` + +* Retrieve the sources:: + + git clone https://pagure.io/pagure.git + cd pagure + +* Install dependencies + + * create the virtualenv:: + + virtualenv pagure_env + source ./pagure_env/bin/activate + + * Install the correct version of pygit2:: + + pip install pygit2==.* + + So in our example:: + + pip install pygit2==0.23.* + + * Install the rest of the dependencies:: + + pip install -r requirements.txt + + +* Create the folder that will receive the projects, forks, docs, requests and + tickets' git repo:: + + mkdir {repos,docs,forks,tickets,requests} + + +* Create the inital database scheme:: + + python createdb.py + + +* Run it:: + + ./runserver.py + + +* To get some profiling information you can also run it as:: + + ./runserver.py --profile + + +This will launch the application at http://127.0.0.1:5000 diff --git a/UPGRADING.rst b/UPGRADING.rst new file mode 100644 index 0000000..3ebb508 --- /dev/null +++ b/UPGRADING.rst @@ -0,0 +1,187 @@ +Upgrading Pagure +================ + + +2.3.4 +----- + +Release 2.3.4 contains an important security fix, blocking a source of XSS +attack. (CVE-2016-1000037) + + + +From 2.2 to 2.3 +--------------- + +2.3 brings a few changes impacting the database scheme, including a new +`duplicate` status for tickets, a feature allowing one to `watch` or +`unwatch` a project and notifications on tickets as exist on pull-requests. + +Therefore, when upgrading from 2.2.x to 2.3, you will have to : + +* Create the new DB tables and the new status field using the ``createdb.py`` script. + +* Update the database schame using alembic: ``alembic upgrade head`` + +This update also brings a new configuration key: + +* ``PAGURE_ADMIN_USERS`` allows to mark some users as instance-wide admins, giving + them full access to every projects, private or not. This feature can then be + used as a way to clean spams. +* ``SMTP_PORT`` allows to specify the port to use when contacting the SMTP + server +* ``SMTP_SSL`` allows to specify whether to use SSL when contacting the SMTP + server +* ``SMTP_USERNAME`` and ``SMTP_PASSWORD`` if provided together allow to contact + an SMTP requiring authentication. + +In this update is also added the script ``api_key_expire_mail.py`` meant to be +run by a daily cron job and warning users when their API token is nearing its +expiration date. + + + +2.2.2 +----- + +Release 2.2.2 contains an important security fix, blocking a source of XSS +attack. + + + +From 2.1 to 2.2 +--------------- + +2.2 brings a number of bug fixes and a few improvements. + +One of the major changes impacts the databases where we must change some of the +table so that the foreign key cascade on delete (fixes deleting a project when a +few plugins were activated). + +When upgrading for 2.1 to 2.2 all you will have to do is: + +* Update the database scheme using alembic: ``alembic upgrade head`` + +.. note:: If you run another database system than PostgreSQL the alembic + revision ``317a285e04a8_delete_hooks.py`` will require adjustment as the + foreign key constraints are named and the names are driver dependant. + + + +From 2.0 to 2.1 +--------------- + +2.1 brings its usual flow of improvements and bug fixes. + +When upgrading from 2.0.x to 2.1 all you will have to: + +* Update the database schame using alembic: ``alembic upgrade head`` + + + +From 1.x to 2.0 +--------------- + +As the version change indicates, 2.0 brings quite a number of changes, +including some that are not backward compatible. + +When upgrading to 2.0 you will have to: + +* Update the database schema using alembic: ``alembic upgrade head`` + +* Create the new DB tables so that the new plugins work using the + ``createdb.py`` script + +* Move the forks git repo + +Forked git repos are now located under the same folder as the regular git +repos, just under a ``forks/`` subfolder. +So the structure changes from: :: + + repos/ + ├── foo.git + └── bar.git + + forks/ + ├── patrick/ + │  ├── test.git + │  └── ipsilon.git + └── pingou/ + ├── foo.git + └── bar.git + +to: :: + + repos/ + ├── foo.git + ├── bar.git + └── forks/ + ├── patrick/ + │  ├── test.git + │  └── ipsilon.git + └── pingou/ + ├── foo.git + └── bar.git + +So the entire ``forks`` folder is moved under the ``repos`` folder where +the other repositories are, containing the sources of the projects. + + +Git repos for ``tickets``, ``requests`` and ``docs`` will be trickier to +move as the structure changes from: :: + + tickets/ + ├── foo.git + ├── bar.git + ├── patrick/ + │  ├── test.git + │  └── ipsilon.git + └── pingou/ + ├── foo.git + └── bar.git + +to: :: + + tickets/ + ├── foo.git + ├── bar.git + └── forks/ + ├── patrick/ + │  ├── test.git + │  └── ipsilon.git + └── pingou/ + ├── foo.git + └── bar.git + +Same for the ``requests`` and the ``docs`` git repos. + +As you can see in the ``tickets``, ``requests`` and ``docs`` folders there +are two types of folders, git repos which are folder with a name ending +with ``.git``, and folder corresponding to usernames. These last ones are +the ones to be moved into a subfolder ``forks/``. + +This can be done using something like: :: + + mkdir forks + for i in `ls -1 |grep -v '\.git'`; do mv $i forks/; done + +* Re-generate the gitolite configuration. + +This can be done via the ``Re-generate gitolite ACLs file`` button in the +admin page. + +* Keep URLs backward compatible + +The support of pseudo-namespace in pagure 2.0 has required some changes +to the URL schema: +https://pagure.io/pagure/053d8cc95fcd50c23a8b0a7f70e55f8d1cc7aebb +became: +https://pagure.io/pagure/c/053d8cc95fcd50c23a8b0a7f70e55f8d1cc7aebb +(Note the added /c/ in it) + +We introduced a backward compatibility fix for this. + +This fix is however *disabled* by default so if you wish to keep the URLs +valid, you will need to adjust you configuration file to include: :: + + OLD_VIEW_COMMIT_ENABLED = True diff --git a/alembic/env.py b/alembic/env.py new file mode 100644 index 0000000..712b616 --- /dev/null +++ b/alembic/env.py @@ -0,0 +1,71 @@ +from __future__ import with_statement +from alembic import context +from sqlalchemy import engine_from_config, pool +from logging.config import fileConfig + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure(url=url, target_metadata=target_metadata) + + with context.begin_transaction(): + context.run_migrations() + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + engine = engine_from_config( + config.get_section(config.config_ini_section), + prefix='sqlalchemy.', + poolclass=pool.NullPool) + + connection = engine.connect() + context.configure( + connection=connection, + target_metadata=target_metadata + ) + + try: + with context.begin_transaction(): + context.run_migrations() + finally: + connection.close() + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() + diff --git a/alembic/script.py.mako b/alembic/script.py.mako new file mode 100644 index 0000000..9570201 --- /dev/null +++ b/alembic/script.py.mako @@ -0,0 +1,22 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision} +Create Date: ${create_date} + +""" + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/alembic/versions/15ea3c2cf83d_pr_comment_editing.py b/alembic/versions/15ea3c2cf83d_pr_comment_editing.py new file mode 100644 index 0000000..ad3eedc --- /dev/null +++ b/alembic/versions/15ea3c2cf83d_pr_comment_editing.py @@ -0,0 +1,45 @@ +"""Adding column to store edited_by and edited_on a PR comment + +Revision ID: 15ea3c2cf83d +Revises: 1cd0a853c697 +Create Date: 2015-11-09 16:18:47.192088 + +""" + +# revision identifiers, used by Alembic. +revision = '15ea3c2cf83d' +down_revision = '1cd0a853c697' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the columns editor_id and edited_on to the table + pull_request_comments. + ''' + + op.add_column( + 'pull_request_comments', + sa.Column( + 'editor_id', + sa.Integer, + sa.ForeignKey('users.id', onupdate='CASCADE'), + nullable=True) + ) + + op.add_column( + 'pull_request_comments', + sa.Column( + 'edited_on', + sa.DateTime, + nullable=True) + ) + + +def downgrade(): + ''' Remove the columns editor_id and edited_on from the table + pull_request_comments. + ''' + op.drop_column('pull_request_comments', 'editor_id') + op.drop_column('pull_request_comments', 'edited_on') diff --git a/alembic/versions/1b6d7dc5600a_versioning_passwords.py b/alembic/versions/1b6d7dc5600a_versioning_passwords.py new file mode 100644 index 0000000..e1c0a13 --- /dev/null +++ b/alembic/versions/1b6d7dc5600a_versioning_passwords.py @@ -0,0 +1,38 @@ +"""versioning_passwords + +Revision ID: 1b6d7dc5600a +Revises: 3b441ef4e928 +Create Date: 2016-01-13 07:57:23.465676 + +""" + +# revision identifiers, used by Alembic. +revision = '1b6d7dc5600a' +down_revision = '3b441ef4e928' + +from alembic import op +import sqlalchemy as sa +import sqlalchemy.orm + +try: + from pagure.lib import model +except ImportError: + import sys + sys.path.insert(0, '.') + from pagure.lib import model + + +def upgrade(): + engine = op.get_bind() + Session = sqlalchemy.orm.scoped_session(sqlalchemy.orm.sessionmaker()) + Session.configure(bind=engine) + session = Session() + for user in session.query(model.User).filter( + model.User.password != None).all(): + user.password = '$1$%s' % user.password + session.add(user) + session.commit() + + +def downgrade(): + raise ValueError("Password can not be downgraded") diff --git a/alembic/versions/1cd0a853c697_add_closed_at_field_in_pr.py b/alembic/versions/1cd0a853c697_add_closed_at_field_in_pr.py new file mode 100644 index 0000000..d6eef26 --- /dev/null +++ b/alembic/versions/1cd0a853c697_add_closed_at_field_in_pr.py @@ -0,0 +1,37 @@ +"""Add closed_at field in PR + + +Revision ID: 1cd0a853c697 +Revises: 6190226bed0 +Create Date: 2015-10-02 09:32:15.370676 + +""" + +# revision identifiers, used by Alembic. +revision = '1cd0a853c697' +down_revision = '6190226bed0' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column closed_at to the table pull_requests. + ''' + op.add_column( + 'pull_requests', + sa.Column( + 'closed_at', + sa.DateTime, + nullable=True, + ) + ) + + op.execute('''UPDATE "pull_requests" SET closed_at=date_created ''' + '''WHERE STATUS != 'Open';''') + + +def downgrade(): + ''' Remove the column closed_at from the table pull_requests. + ''' + op.drop_column('pull_requests', 'closed_at') diff --git a/alembic/versions/1f3de3853a1a_add_the_tree_id_column_to_pr_inline_.py b/alembic/versions/1f3de3853a1a_add_the_tree_id_column_to_pr_inline_.py new file mode 100644 index 0000000..eb41dcd --- /dev/null +++ b/alembic/versions/1f3de3853a1a_add_the_tree_id_column_to_pr_inline_.py @@ -0,0 +1,29 @@ +"""Add the tree_id column to PR inline comments + +Revision ID: 1f3de3853a1a +Revises: 58e60d869326 +Create Date: 2016-02-22 16:13:59.943083 + +""" + +# revision identifiers, used by Alembic. +revision = '1f3de3853a1a' +down_revision = '58e60d869326' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column tree_id to the table pull_request_comments. + ''' + op.add_column( + 'pull_request_comments', + sa.Column('tree_id', sa.String(40), nullable=True) + ) + + +def downgrade(): + ''' Remove the column tree_id from the table pull_request_comments. + ''' + op.drop_column('pull_request_comments', 'tree_id') diff --git a/alembic/versions/22db0a833d35_add_notifications_to_tickets.py b/alembic/versions/22db0a833d35_add_notifications_to_tickets.py new file mode 100644 index 0000000..20bfad7 --- /dev/null +++ b/alembic/versions/22db0a833d35_add_notifications_to_tickets.py @@ -0,0 +1,33 @@ +"""Add notifications to tickets + +Revision ID: 22db0a833d35 +Revises: 317a285e04a8 +Create Date: 2016-06-27 16:10:33.395495 + +""" + +# revision identifiers, used by Alembic. +revision = '22db0a833d35' +down_revision = '317a285e04a8' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column notification to the table issue_comments. + ''' + op.add_column( + 'issue_comments', + sa.Column('notification', sa.Boolean, default=False, nullable=True) + ) + op.execute('''UPDATE "issue_comments" SET notification=False;''') + op.alter_column( + 'issue_comments', 'notification', + nullable=False, existing_nullable=True) + + +def downgrade(): + ''' Remove the column notification from the table issue_comments. + ''' + op.drop_column('issue_comments', 'notification') diff --git a/alembic/versions/257a7ce22682_add_the_remote_git_entry.py b/alembic/versions/257a7ce22682_add_the_remote_git_entry.py new file mode 100644 index 0000000..623568c --- /dev/null +++ b/alembic/versions/257a7ce22682_add_the_remote_git_entry.py @@ -0,0 +1,46 @@ +"""Add the remote_git entry + +Revision ID: 257a7ce22682 +Revises: 36116bb7a69b +Create Date: 2015-07-21 14:26:23.989220 + +""" + +# revision identifiers, used by Alembic. +revision = '257a7ce22682' +down_revision = '36116bb7a69b' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column remote_git to the table pull_requests and make the + project_id_from field nullable. + ''' + op.add_column( + 'pull_requests', + sa.Column('remote_git', sa.Text, nullable=True) + ) + op.alter_column( + 'pull_requests', + column_name='project_id_from', + nullable=True, + existing_nullable=False) + op.create_check_constraint( + "ck_lcl_or_remo_pr", + "pull_requests", + 'NOT(project_id_from IS NULL AND remote_git IS NULL)' + ) + + +def downgrade(): + ''' Remove the column remote_git from the table pull_requests and make + the project_id_from field not nullable. + ''' + op.drop_column('pull_requests', 'remote_git') + op.alter_column( + 'pull_requests', + column_name='project_id_from', + nullable=False, + existing_nullable=True) diff --git a/alembic/versions/298891e63039_change_the_status_of_pull_requests.py b/alembic/versions/298891e63039_change_the_status_of_pull_requests.py new file mode 100644 index 0000000..beb05b9 --- /dev/null +++ b/alembic/versions/298891e63039_change_the_status_of_pull_requests.py @@ -0,0 +1,60 @@ +"""Change the status of pull_requests + + +Revision ID: 298891e63039 +Revises: 3c25e14b855b +Create Date: 2015-06-08 13:06:11.938966 + +""" + +# revision identifiers, used by Alembic. +revision = '298891e63039' +down_revision = '3c25e14b855b' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Adjust the status column of the pull_requests table. + ''' + op.add_column( + 'pull_requests', + sa.Column( + '_status', sa.Text, + sa.ForeignKey( + 'status_pull_requests.status', onupdate='CASCADE'), + default='Open', + nullable=True) + ) + + op.execute('''UPDATE "pull_requests" ''' + '''SET _status='Open' WHERE status=TRUE;''') + op.execute('''UPDATE "pull_requests" ''' + '''SET _status='Merged' WHERE status=FALSE;''') + + op.drop_column('pull_requests', 'status') + op.alter_column( + 'pull_requests', + column_name='_status', new_column_name='status', + nullable=False, existing_nullable=True) + + +def downgrade(): + ''' Revert the status column of the pull_requests table. + ''' + op.add_column( + 'pull_requests', + sa.Column( + '_status', sa.Boolean, default=True, nullable=True) + ) + op.execute('''UPDATE "pull_requests" ''' + '''SET _status=TRUE WHERE status='Open';''') + op.execute('''UPDATE "pull_requests" ''' + '''SET _status=FALSE WHERE status!='Open';''') + + op.drop_column('pull_requests', 'status') + op.alter_column( + 'pull_requests', + column_name='_status', new_column_name='status', + nullable=False, existing_nullable=True) diff --git a/alembic/versions/2aa7b3958bc5_add_the_milestones_column.py b/alembic/versions/2aa7b3958bc5_add_the_milestones_column.py new file mode 100644 index 0000000..5a00dbb --- /dev/null +++ b/alembic/versions/2aa7b3958bc5_add_the_milestones_column.py @@ -0,0 +1,31 @@ +"""Add the milestones column + +Revision ID: 2aa7b3958bc5 +Revises: 443e090da188 +Create Date: 2016-05-03 15:59:04.992414 + +""" + +# revision identifiers, used by Alembic. +revision = '2aa7b3958bc5' +down_revision = '443e090da188' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column _milestones to the table projects + and the column milestone to the table issues. + ''' + op.add_column( + 'projects', + sa.Column('_milestones', sa.Text, nullable=True) + ) + + +def downgrade(): + ''' Drop the column _milestones from the table projects + and the column milestone from the table issues. + ''' + op.drop_column('projects', '_milestones') diff --git a/alembic/versions/317a285e04a8_delete_hooks.py b/alembic/versions/317a285e04a8_delete_hooks.py new file mode 100644 index 0000000..2ee64e2 --- /dev/null +++ b/alembic/versions/317a285e04a8_delete_hooks.py @@ -0,0 +1,59 @@ +"""Delete hooks + +Revision ID: 317a285e04a8 +Revises: 2aa7b3958bc5 +Create Date: 2016-05-30 11:28:48.512577 + +""" + +# revision identifiers, used by Alembic. +revision = '317a285e04a8' +down_revision = '2aa7b3958bc5' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + """ Alter the hooks table to update the foreign key to cascade on delete. + """ + + for table in [ + 'hook_fedmsg', 'hook_irc', 'hook_mail', + 'hook_pagure_force_commit', 'hook_pagure', 'hook_pagure_requests', + 'hook_pagure_tickets', 'hook_pagure_unsigned_commit', 'hook_rtd', + ]: + op.drop_constraint( + '%s_project_id_fkey' % table, + table, + type_='foreignkey') + op. create_foreign_key( + name='%s_project_id_fkey' % table, + source_table=table, + referent_table='projects', + local_cols=['project_id'], + remote_cols=['id'], + onupdate='cascade', + ondelete='cascade', + ) + + op.drop_constraint( + 'projects_groups_project_id_fkey', + 'projects_groups', + type_='foreignkey') + op. create_foreign_key( + name='projects_groups_project_id_fkey', + source_table='projects_groups', + referent_table='projects', + local_cols=['project_id'], + remote_cols=['id'], + onupdate='cascade', + ondelete='cascade', + ) + + + +def downgrade(): + """ Alter the hooks table to update the foreign key to undo the cascade + on delete. + """ diff --git a/alembic/versions/36116bb7a69b_add_the_url_field_to_project.py b/alembic/versions/36116bb7a69b_add_the_url_field_to_project.py new file mode 100644 index 0000000..27784ef --- /dev/null +++ b/alembic/versions/36116bb7a69b_add_the_url_field_to_project.py @@ -0,0 +1,29 @@ +"""Add the url field to project + +Revision ID: 36116bb7a69b +Revises: abc71fd60fa +Create Date: 2015-06-11 12:36:33.544046 + +""" + +# revision identifiers, used by Alembic. +revision = '36116bb7a69b' +down_revision = 'abc71fd60fa' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column url to the table projects. + ''' + op.add_column( + 'projects', + sa.Column('url', sa.Text, nullable=True) + ) + + +def downgrade(): + ''' Remove the column merge_status from the table projects. + ''' + op.drop_column('projects', 'url') diff --git a/alembic/versions/3b441ef4e928_comment_editing_issue.py b/alembic/versions/3b441ef4e928_comment_editing_issue.py new file mode 100644 index 0000000..60f5e87 --- /dev/null +++ b/alembic/versions/3b441ef4e928_comment_editing_issue.py @@ -0,0 +1,44 @@ +"""Adding column to store edited_by and edited_on a issue comment + +Revision ID: 3b441ef4e928 +Revises: 15ea3c2cf83d +Create Date: 2015-12-03 12:34:28.316699 + +""" + +# revision identifiers, used by Alembic. +revision = '3b441ef4e928' +down_revision = '15ea3c2cf83d' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the columns editor_id and edited_on to the table issue_comments. + ''' + + op.add_column( + 'issue_comments', + sa.Column( + 'editor_id', + sa.Integer, + sa.ForeignKey('users.id', onupdate='CASCADE'), + nullable=True) + ) + + op.add_column( + 'issue_comments', + sa.Column( + 'edited_on', + sa.DateTime, + nullable=True) + ) + + +def downgrade(): + ''' Remove the columns editor_id and edited_on from the table + issue_comments. + ''' + op.drop_column('issue_comments', 'editor_id') + op.drop_column('issue_comments', 'edited_on') diff --git a/alembic/versions/3c25e14b855b_add_an_avatar_email_for_project.py b/alembic/versions/3c25e14b855b_add_an_avatar_email_for_project.py new file mode 100644 index 0000000..407f2d4 --- /dev/null +++ b/alembic/versions/3c25e14b855b_add_an_avatar_email_for_project.py @@ -0,0 +1,29 @@ +"""add an avatar email for project + +Revision ID: 3c25e14b855b +Revises: b5efae6bb23 +Create Date: 2015-06-08 12:05:13.832348 + +""" + +# revision identifiers, used by Alembic. +revision = '3c25e14b855b' +down_revision = 'b5efae6bb23' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column merge_status to the table projects. + ''' + op.add_column( + 'projects', + sa.Column('avatar_email', sa.Text, nullable=True) + ) + + +def downgrade(): + ''' Remove the column merge_status from the table projects. + ''' + op.drop_column('projects', 'avatar_email') diff --git a/alembic/versions/43df5e588a87_add_closed_at_attribute_in_issues.py b/alembic/versions/43df5e588a87_add_closed_at_attribute_in_issues.py new file mode 100644 index 0000000..5ceb1da --- /dev/null +++ b/alembic/versions/43df5e588a87_add_closed_at_attribute_in_issues.py @@ -0,0 +1,27 @@ +"""add_closed_at_attribute_in_issues + +Revision ID: 43df5e588a87 +Revises: 22db0a833d35 +Create Date: 2016-06-28 22:59:36.653905 + +""" + +# revision identifiers, used by Alembic. +revision = '43df5e588a87' +down_revision = '22db0a833d35' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add closed_at column in issues table ''' + op.add_column( + 'issues', + sa.Column('closed_at', sa.DateTime, nullable=True) + ) + + +def downgrade(): + ''' Remove the closed_at column in issues table ''' + op.drop_column('issues', 'closed_at') diff --git a/alembic/versions/443e090da188_up_to_255_characters_for_project_name.py b/alembic/versions/443e090da188_up_to_255_characters_for_project_name.py new file mode 100644 index 0000000..5518b30 --- /dev/null +++ b/alembic/versions/443e090da188_up_to_255_characters_for_project_name.py @@ -0,0 +1,32 @@ +"""up to 255 characters for project.name + +Revision ID: 443e090da188 +Revises: 496f7a700f2e +Create Date: 2016-04-20 17:57:36.385103 + +""" + +# revision identifiers, used by Alembic. +revision = '443e090da188' +down_revision = '496f7a700f2e' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.alter_column( + table_name='projects', + column_name='name', + type_=sa.String(255), + existing_type=sa.String(32) + ) + + +def downgrade(): + op.alter_column( + table_name='projects', + column_name='name', + type_=sa.String(32), + existing_type=sa.String(255) + ) diff --git a/alembic/versions/496f7a700f2e_add_priorities.py b/alembic/versions/496f7a700f2e_add_priorities.py new file mode 100644 index 0000000..21c7ca7 --- /dev/null +++ b/alembic/versions/496f7a700f2e_add_priorities.py @@ -0,0 +1,37 @@ +"""Add priorities + +Revision ID: 496f7a700f2e +Revises: 4cae55a80a42 +Create Date: 2016-03-24 12:19:34.298752 + +""" + +# revision identifiers, used by Alembic. +revision = '496f7a700f2e' +down_revision = '4cae55a80a42' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column _priorities to the table projects + and the column priority to the table issues. + ''' + op.add_column( + 'projects', + sa.Column('_priorities', sa.Text, nullable=True) + ) + + op.add_column( + 'issues', + sa.Column('priority', sa.Integer, nullable=True, default=None) + ) + + +def downgrade(): + ''' Drop the column _priorities from the table projects + and the column priority from the table issues. + ''' + op.drop_column('projects', '_priorities') + op.drop_column('issues', 'priority') diff --git a/alembic/versions/4cae55a80a42_add_the_initial_comment_on_the_pr_table.py b/alembic/versions/4cae55a80a42_add_the_initial_comment_on_the_pr_table.py new file mode 100644 index 0000000..c329944 --- /dev/null +++ b/alembic/versions/4cae55a80a42_add_the_initial_comment_on_the_pr_table.py @@ -0,0 +1,29 @@ +"""Add the initial_comment on the PR table + +Revision ID: 4cae55a80a42 +Revises: 1f3de3853a1a +Create Date: 2016-03-01 12:00:34.823097 + +""" + +# revision identifiers, used by Alembic. +revision = '4cae55a80a42' +down_revision = '1f3de3853a1a' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column initial_comment to the table pull_requests. + ''' + op.add_column( + 'pull_requests', + sa.Column('initial_comment', sa.Text, nullable=True) + ) + + +def downgrade(): + ''' Remove the column initial_comment from the table pull_requests. + ''' + op.drop_column('pull_requests', 'initial_comment') diff --git a/alembic/versions/58e60d869326_add_notification_bool_to_pr.py b/alembic/versions/58e60d869326_add_notification_bool_to_pr.py new file mode 100644 index 0000000..2c7603b --- /dev/null +++ b/alembic/versions/58e60d869326_add_notification_bool_to_pr.py @@ -0,0 +1,33 @@ +"""add notification bool to PR + +Revision ID: 58e60d869326 +Revises: 1b6d7dc5600a +Create Date: 2016-02-12 12:39:07.839530 + +""" + +# revision identifiers, used by Alembic. +revision = '58e60d869326' +down_revision = '1b6d7dc5600a' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column notification to the table pull_request_comments. + ''' + op.add_column( + 'pull_request_comments', + sa.Column('notification', sa.Boolean, default=False, nullable=True) + ) + op.execute('''UPDATE "pull_request_comments" SET notification=False;''') + op.alter_column( + 'pull_request_comments', 'notification', + nullable=False, existing_nullable=True) + + +def downgrade(): + ''' Remove the column notification from the table pull_request_comments. + ''' + op.drop_column('pull_request_comments', 'notification') diff --git a/alembic/versions/6190226bed0_add_the_updated_on_column_to_pull_.py b/alembic/versions/6190226bed0_add_the_updated_on_column_to_pull_.py new file mode 100644 index 0000000..6a362b6 --- /dev/null +++ b/alembic/versions/6190226bed0_add_the_updated_on_column_to_pull_.py @@ -0,0 +1,43 @@ +"""Add the updated_on column to pull-requests + +Revision ID: 6190226bed0 +Revises: 257a7ce22682 +Create Date: 2015-09-29 15:32:58.229183 + +""" + +# revision identifiers, used by Alembic. +revision = '6190226bed0' +down_revision = '257a7ce22682' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column updated_on to the table pull_requests. + ''' + op.add_column( + 'pull_requests', + sa.Column( + 'updated_on', + sa.DateTime, + nullable=True, + default=sa.func.now(), + onupdate=sa.func.now() + ) + ) + + op.execute('''UPDATE "pull_requests" SET updated_on=date_created;''') + + op.alter_column( + 'pull_requests', + column_name='updated_on', + nullable=False, + existing_nullable=True) + + +def downgrade(): + ''' Remove the column updated_on from the table pull_requests. + ''' + op.drop_column('pull_requests', 'updated_on') diff --git a/alembic/versions/abc71fd60fa_add_the_closed_by_column_to_pull_.py b/alembic/versions/abc71fd60fa_add_the_closed_by_column_to_pull_.py new file mode 100644 index 0000000..17e5ff6 --- /dev/null +++ b/alembic/versions/abc71fd60fa_add_the_closed_by_column_to_pull_.py @@ -0,0 +1,34 @@ +"""Add the closed_by column to pull_requests + + +Revision ID: abc71fd60fa +Revises: 298891e63039 +Create Date: 2015-06-08 16:06:18.017110 + +""" + +# revision identifiers, used by Alembic. +revision = 'abc71fd60fa' +down_revision = '298891e63039' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + ''' Add the column merge_status to the table pull_requests. + ''' + op.add_column( + 'pull_requests', + sa.Column( + 'closed_by_id', + sa.Integer, + sa.ForeignKey('users.id', onupdate='CASCADE'), + ) + ) + + +def downgrade(): + ''' Remove the column merge_status from the table pull_requests. + ''' + op.drop_column('pull_requests', 'closed_by_id') diff --git a/alembic/versions/b5efae6bb23_add_merge_status_to_the_pull_requests_.py b/alembic/versions/b5efae6bb23_add_merge_status_to_the_pull_requests_.py new file mode 100644 index 0000000..aa12d31 --- /dev/null +++ b/alembic/versions/b5efae6bb23_add_merge_status_to_the_pull_requests_.py @@ -0,0 +1,35 @@ +"""Add merge status to the pull_requests table + +Revision ID: b5efae6bb23 +Revises: None +Create Date: 2015-06-02 16:30:06.199128 + +""" + +# revision identifiers, used by Alembic. +revision = 'b5efae6bb23' +down_revision = None + +from alembic import op +from sqlalchemy.dialects.postgresql import ENUM +import sqlalchemy as sa + + +# Sources for the code: https://bitbucket.org/zzzeek/alembic/issue/67 + +def upgrade(): + ''' Add the column merge_status to the table pull_requests. + ''' + enum = ENUM('NO_CHANGE', 'FFORWARD', 'CONFLICTS', 'MERGE', + name='merge_status_enum', create_type=False) + enum.create(op.get_bind(), checkfirst=False) + op.add_column( + 'pull_requests', + sa.Column('merge_status', enum, nullable=True) + ) + +def downgrade(): + ''' Remove the column merge_status from the table pull_requests. + ''' + ENUM(name="merge_status_enum").drop(op.get_bind(), checkfirst=False) + op.drop_column('pull_requests', 'merge_status') diff --git a/createdb.py b/createdb.py new file mode 100644 index 0000000..eb78c78 --- /dev/null +++ b/createdb.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python2 + +# These two lines are needed to run on EL6 +__requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4'] +import pkg_resources + +from pagure import APP +from pagure.lib import model + +model.create_tables( + APP.config['DB_URL'], + APP.config.get('PATH_ALEMBIC_INI', None), + acls=APP.config.get('ACLS', {}), + debug=True) diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..73658c9 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pagure.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pagure.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/pagure" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pagure" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/doc/_static/overview.png b/doc/_static/overview.png new file mode 100644 index 0000000..32c8531 Binary files /dev/null and b/doc/_static/overview.png differ diff --git a/doc/_static/overview_simple.png b/doc/_static/overview_simple.png new file mode 100644 index 0000000..f898461 Binary files /dev/null and b/doc/_static/overview_simple.png differ diff --git a/doc/_static/pagure.png b/doc/_static/pagure.png new file mode 100644 index 0000000..cd8ed7f Binary files /dev/null and b/doc/_static/pagure.png differ diff --git a/doc/_static/site.css b/doc/_static/site.css new file mode 100644 index 0000000..44dc318 --- /dev/null +++ b/doc/_static/site.css @@ -0,0 +1,16 @@ +@import url("cloud.css"); +@import url("http://fonts.googleapis.com/css?family=Comfortaa"); + +.pagure-logo span { + background: url("pagure.png") no-repeat scroll 50% 0 transparent; + display: block; + width: 134px; + height: 64px; + margin-bottom: 10px; + } + +h1.pagure-logo { + font-family: 'Comfortaa', sans-serif; + margin-top: -10px; + margin-bottom: 12px; +} diff --git a/doc/_templates/pagure-logo.html b/doc/_templates/pagure-logo.html new file mode 100644 index 0000000..49b558a --- /dev/null +++ b/doc/_templates/pagure-logo.html @@ -0,0 +1 @@ +

Pagure

diff --git a/doc/api.rst b/doc/api.rst new file mode 100644 index 0000000..8edbf23 --- /dev/null +++ b/doc/api.rst @@ -0,0 +1,5 @@ +Pagure API +========== + +The API documentation can be found at https://pagure.io/api/0/ or within +the sources in ``pagure/doc/api.rst``. diff --git a/doc/conf.py b/doc/conf.py new file mode 100644 index 0000000..52be776 --- /dev/null +++ b/doc/conf.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- +# +# pagure documentation build configuration file, created by +# sphinx-quickstart on Wed Mar 1 10:30:13 2015. +# +# This file is execfile()d with the current directory set to its containing +# dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import os +import re + +pagurefile = os.path.join( + os.path.dirname(__file__), '..', 'pagure', '__init__.py') + +# Thanks to SQLAlchemy: +# https://github.com/zzzeek/sqlalchemy/blob/master/setup.py#L104 +with open(pagurefile) as stream: + VERSION = re.compile( + r".*__version__ = '(.*?)'", re.S + ).match(stream.read()).group(1) + +# If extensions (or modules to document with autodoc) are in another +# directory, add these directories to sys.path here. If the directory is +# relative to the documentation root, use os.path.abspath to make it +# absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ------------------------------------------------ + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.coverage', 'sphinx.ext.viewcode' +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'pagure' +copyright = u'2015, Red Hat Inc, Pierre-Yves Chibon ' + +# The version info for the project you're documenting, acts as replacement +# for |version| and |release|, also used in various other places throughout +# the built documents. +# +# The short X.Y version. +#version = __version__ +version = VERSION +# The full version, including alpha/beta/rc tags. +#release = '1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all +# documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output ---------------------------------------------- + +import cloud_sptheme as csp + +html_style = 'site.css' + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'default' +html_theme = "cloud" + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +html_theme_options = { + "sidebarwidth": "200px", + "max_width": "900px", + "compact_width": "800px", + "minimal_width": "700px", + + # Style it like Fedora.. + "bodyfont": "Cantarell", + + "highlightcolor": "#79db32", # First Green + + "sidebarbgcolor": "#FEFEFE", + "sidebartrimcolor": "#FEFEFE", + + "sectionbgcolor": "#FEFEFE", + "sectiontrimcolor": "#FEFEFE", + "sectiontextcolor": "#444444", + + "relbarbgcolor": "#FEFEFE", + "relbartextcolor": "#444444", + "relbarlinkcolor": "#444444", + + "bgcolor": "#FEFEFE", + "textcolor": "#444444", + #"linkcolor": "#79db32", # First Green + "linkcolor": "#00009d", + + "headtextcolor": "#444444", + "headlinkcolor": "#444444", + + #"codebgcolor" + #"codetextcolor" + "codetrimcolor": "#79db32", # First Green + + "footerbgcolor": "#FEFEFE", +} +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] +html_theme_path = [csp.get_theme_dir()] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +html_sidebars = { + '**': [ + 'pagure-logo.html', + 'localtoc.html', + 'relations.html', + 'sourcelink.html', + 'searchbox.html', + ] +} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'pagure' + +# -- Options for LaTeX output --------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + #'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + #'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + #'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ( + 'index', 'pagure.tex', u'Pagure Documentation', + u'Pierre-Yves Chibon \\textless{}pingou@pingoured.fr\\textgreater{}', + 'manual' + ), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + +# -- Options for manual page output --------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ( + 'index', 'pagure', u'Pagure Documentation', + [u'Pierre-Yves Chibon '], + 1 + ) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ( + 'index', 'pagure', u'Pagure Documentation', + u'Pierre-Yves Chibon ', 'pagure', + 'Small git-centric forge', + 'Miscellaneous' + ), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' diff --git a/doc/configuration.rst b/doc/configuration.rst new file mode 100644 index 0000000..eec82c7 --- /dev/null +++ b/doc/configuration.rst @@ -0,0 +1,508 @@ +Configuration +============= + +Pagure offers a wide varieties of options that must or can be used to +adjust its behavior. + + +Must options +------------ + +Here are the options you must set up in order to get pagure running. + + +SECRET_KEY +~~~~~~~~~~ + +This key is used by flask to create the session. It should be kept secret +and set as a long and random string. + + +SALT_EMAIL +~~~~~~~~~~ + +This key is used for when sending notification to ensure that when sending +notifications to different users, each one of them has a different, unique +and un-fakable ``Reply-To`` header that is then used by the milter to find +out if the response received is a real one or a fake/invalid one. + + +DB_URL +~~~~~~ + +This key indicates to the framework how and where to connect to the database +server. Pagure using `SQLAchemy `_ it can connect +to a wide range of database server including MySQL, PostgreSQL and SQLite. + +Examples values: + +:: + + DB_URL=mysql://user:pass@host/db_name + DB_URL=postgres://user:pass@host/db_name + DB_URL = 'sqlite:////var/tmp/pagure_dev.sqlite' + +Defaults to ``sqlite:////var/tmp/pagure_dev.sqlite`` + + +APP_URL +~~~~~~~ + +This key indicates the URL at which this pagure instance will be made available. + +Defaults to: ``https://pagure.org/`` + + +EMAIL_ERROR +~~~~~~~~~~~ + +Pagure sends email when it caches an un-expected error (which saves you from +having to monitor the logs regularly but if you like, the error is still +present in the logs). +This setting allows you to specify to which email address to send these error +reports. + + +GIT_URL_SSH +~~~~~~~~~~~ + +This configuration key provides the information to the user on how to clone +the git repos hosted on pagure via `SSH `_. + +The URL should end with a slash ``/``. + +Defaults to: ``'ssh://git@pagure.org/'`` + + +GIT_URL_GIT +~~~~~~~~~~~ +This configuration key provides the information to the user on how to clone +the git repos hosted on pagure anonymously. This access can be granted via +the ``git://`` or ``http(s)://`` protocols. + +The URL should end with a slash ``/``. + +Defaults to: ``'git://pagure.org/'`` + + +GIT_FOLDER +~~~~~~~~~~ + +This configuration key points to where the folders containing the git repos +of the projects are located. + +Each project in pagure has 4 git repositories: + +- the main repo for the code +- the doc repo showed in the doc server +- the ticket and request repos storing the metadata of the + tickets/pull-requests + +There are then another 2 folders specifying the locations of the forks and +remote git repo used for the remotes pull-requests (ie: pull-request coming +from a project not hosted on this instance of pagure). + + +FORK_FOLDER +~~~~~~~~~~~ + +This configuration key points to the folder where the git repos of forks of +the projects are stored. + + +DOCS_FOLDER +~~~~~~~~~~~ + +This configuration key points to the folder where the git repos for the +documentation of the projects are stored. + + +TICKETS_FOLDER +~~~~~~~~~~~~~~ + +This configuration key points to the folder where the git repos storing the +metadata of the tickets opened against the project are stored . + + +REQUESTS_FOLDER +~~~~~~~~~~~~~~~ + +This configuration key points to the folder where the git repos storing the +metadata of the pull-requests opened against the project are stored. + + +REMOTE_GIT_FOLDER +~~~~~~~~~~~~~~~~~ + +This configuration key points to the folder where the remote git repos (ie: +not hosted on pagure) that someone used to open a pull-request against a +project hosted on pagure are stored. + + +SESSION_COOKIE_SECURE +~~~~~~~~~~~~~~~~~~~~~ + +When this is set to True, the session cookie will only be returned to the +server via ssl (https). If you connect to the server via plain http, the +cookie will not be sent. This prevents sniffing of the cookie contents. +This may be set to False when testing your application but should always +be set to True in production. + +Defaults to: ``False`` for development, must be ``True`` in production with +https. + + +FROM_EMAIL +~~~~~~~~~~ + +This setting allows to specify the email address used by this pagure instance +when sending emails (notifications). + +Defaults to: ``pagure@pagure.org`` + + +DOMAIN_EMAIL_NOTIFICATIONS +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This setting allows to specify the domain used by this pagure instance +when sending emails (notifications). More precisely, this setting is used +when building the ``msg-id`` header of the emails sent. + +Defaults to: ``pagure.org`` + + +Configure Gitolite +------------------ + +Pagure uses `gitolite `_ as an authorization layer. +Gitolite relies on `SSH `_ for +the authentication. In other words, SSH let you in and gitolite check if you +are allowed to do what you are trying to do once you are inside. + + +GITOLITE_HOME +~~~~~~~~~~~~~ + +This configuration key should point to the home of the user under which +gitolite is ran. + + +GITOLITE_VERSION +~~~~~~~~~~~~~~~~ + +This configuration key allows to specify which version of gitolite you are +using, it can be either ``2`` or ``3``. + +Defaults to: ``3``. + + +GITOLITE_KEYDIR +~~~~~~~~~~~~~~~ + +This configuration key points to the folder where gitolite stores and accesses +the public SSH keys of all the user have access to the server. + +Since pagure is the user interface, it is pagure that writes down the files +in this directory effectively setting up the users to be able to use gitolite. + + +GL_RC +~~~~~ + +This configuration key must point to the file ``gitolite.rc`` used by gitolite +to record who has access to what (ie: who has access to which repo/branch). + + +GL_BINDIR +~~~~~~~~~ + +This configuration key indicates the folder in which the gitolite tools can +be found. It can be as simple as ``/usr/bin/`` if the tools have been installed +using a package manager or something like ``/opt/bin/`` for a more custom +install. + + +EventSource options +------------------- + +EVENTSOURCE_SOURCE +~~~~~~~~~~~~~~~~~~ + +This configuration key indicates the URL at which the EventSource server is +available. If not defined, pagure will behave as if there are no EventSource +server running. + +EVENTSOURCE_PORT +~~~~~~~~~~~~~~~~ + +This configuration key indicates the port at which the EventSource server is +running. This allows adjusting the port via the configuration file instead +of hard-coding it in the code. + +.. note:: The EventSource server requires a redis server (see ``Redis options`` + below) + + +Web-hooks notifications +----------------------- + +WEBHOOK +~~~~~~~ + +This configuration key allows turning on or off web-hooks notifications for +this pagure instance. + +Defaults to: ``False``. + +.. note:: The Web-hooks server requires a redis server (see ``Redis options`` + below) + + +Redis options +------------- + +REDIS_HOST +~~~~~~~~~~ + +This configuration key indicates the host at which the `redis `_ +server is running. + +Defaults to: ``0.0.0.0``. + +REDIS_PORT +~~~~~~~~~~ + +This configuration key indicates the port at which the reds server can be +contacted. + +Defaults to: ``6379``. + +REDIS_DB +~~~~~~~~ + +This configuration key indicates the name of the redis database to use to +communicate with the EventSource server. + +Defaults to: ``0``. + + +Authentication options +---------------------- + +ADMIN_GROUP +~~~~~~~~~~~ + +List of groups, local or remotes (if the openid server used supports the +group extension), that are site admin. These admins can regenerate the +gitolite configuration, the ssh key files, the hook-token for every project +as well as manage users and groups. + + +PAGURE_ADMIN_USERS +~~~~~~~~~~~~~~~~~~ + +List of usernames that are site admin. These admins have the same rights as +the user in the admin groups (listed above) as well as admin rights to +every projects hosted on this pagure instance. + + +Optional options +---------------- + +SSH_KEYS +~~~~~~~~ + +It is a good pratice to publish the fingerprint and public SSH key of a +server you provide access to. +Pagure offers the possibility to expose this information based on the values +set in the configuration file, in the ``SSH_KEYS`` configuration key. + +See the `SSH hostkeys/Fingerprints page on pagure.io `_. + +.. warning: The format is important + + SSH_KEYS = {'RSA': {'fingerprint': '', 'pubkey': ''}} + +Where `` and `` must be replaced by your values. + + +ITEM_PER_PAGE +~~~~~~~~~~~~~ +This configuration key allows you to configure the length of a page by +setting the number of items on the page. Items can be commits, users, groups +or projects for example. + +Defaults to: ``50``. + + +SMTP_SERVER +~~~~~~~~~~~ + +This configuration key allows to configure the SMTP server to use when +sending emails. + +Defaults to: ``localhost``. + +SMTP_PORT +~~~~~~~~~ + +This configuration key allow to define the SMTP server port. + +SMTP by default uses TCP port 25. The protocol for mail submission is +the same, but uses port 587. +SMTP connections secured by SSL, known as SMTPS, default to port 465 +(nonstandard, but sometimes used for legacy reasons). + +Defaults to: ``25`` + +SMTP_SSL +~~~~~~~~ + +This configuration key allows to specify whether the SMTP connections +should secured over SSL + +Defaults to: ``False`` + +SMTP_USERNAME +~~~~~~~~~~~~~ + +This configuration key allows usage of SMTP with auth + +Note: Specify SMTP_USERNAME and SMTP_PASSWORD for using SMTP auth + +Defaults to: ``None`` + +SMTP_PASSWORD +~~~~~~~~~~~~~ + +This configuration key allows usage of SMTP with auth + +Note: Specify SMTP_USERNAME and SMTP_PASSWORD for using SMTP auth + +Defaults to: ``None`` + +SHORT_LENGTH +~~~~~~~~~~~~ + +This configuration key allows to configure the length of the commit ids or +file hex displayed in the user interface. + +Defaults to: ``6``. + + +BLACKLISTED_PROJECTS +~~~~~~~~~~~~~~~~~~~~ + +This configuration key allows to set a list of project name that are forbidden. +This list is used for example to avoid conflicts at the URL level between the +static files located under ``/static/`` and a project that would be named +``static`` and thus be located at ``/static``. + +Defaults to: + +:: + + [ + 'static', 'pv', 'releases', 'new', 'api', 'settings', + 'logout', 'login', 'users', 'groups' + ] + + + +CHECK_SESSION_IP +~~~~~~~~~~~~~~~~ + +This configuration key allows to configure whether to check the user's IP +address when retrieving its session. This makes things more secure but +under certain setup it might not work (for example if there are proxies +in front of the application). + +Defaults to: ``True``. + + +PAGURE_AUTH +~~~~~~~~~~~~ + +This configuration key allows to specify which authentication method to use. +Pagure supports currently two authentication methods, one relying on the +Fedora Account System `FAS `_, +the other relying on local user accounts. +It can therefore be either ``fas`` or ``local``. + +Defaults to: ``fas``. + + +IP_ALLOWED_INTERNAL +~~~~~~~~~~~~~~~~~~~ + +This configuration key allows to specify which IP addresses are allowed +to access the internal API endpoint. These endpoints are accessed by the +milters for example and allow to perform action in the name of someone else. +So they are sensitive, thus the check for the origin of the request using +these endpoints. + +Defaults to: ``['127.0.0.1', 'localhost', '::1']``. + + +MAX_CONTENT_LENGTH +~~~~~~~~~~~~~~~~~~ + +This configuration key allows to specify the maximum size allowed when +uploading content to pagure (for example, screenshots to a ticket). + +Defaults to: ``4 * 1024 * 1024`` which corresponds to 4 megabytes. + + +ENABLE_TICKETS +~~~~~~~~~~~~~~ + +This configuration key allows to activate or de-activate the ticketing system +for all the projects hosted on this pagure instance. + +Defaults to: ``True`` + + +ENABLE_NEW_PROJECTS +~~~~~~~~~~~~~~~~~~~ + +This configuration key allows to create or forbids creating new projects in +the user interface of this pagure instance. + +Defaults to: ``True`` + + +ENABLE_DEL_PROJECTS +~~~~~~~~~~~~~~~~~~~ + +This configuration key allows to delete or forbids deleting projects in +the user interface of this pagure instance. + +Defaults to: ``True`` + + +EMAIL_SEND +~~~~~~~~~~ + +This configuration key allows turning on or off all email notification for +this pagure instance. This can be useful to turn off when developing on +pagure, or for test or pre-production instances. + +Defaults to: ``True``. + + +OLD_VIEW_COMMIT_ENABLED +~~~~~~~~~~~~~~~~~~~~~~~ + +In version 1.3, pagure changed its URL scheme to view the commit of a +project in order to add support for pseudo-namespaced projects. + +For pagure instances older than 1.3, who care about backward compatibility, +we added an endpoint ``view_commit_old`` that brings URL backward +compatibility for URLs using the complete git hash (the 40 characters). +For URLs using a shorter hash, the URLs will remain broken. + +This configuration key allows turning on or off this backward compatibility +which is useful for pagure instances running since before 1.3 but is not +for newer instances. + +Defaults to: ``False``. diff --git a/doc/contributing.rst b/doc/contributing.rst new file mode 100644 index 0000000..bd6a999 --- /dev/null +++ b/doc/contributing.rst @@ -0,0 +1,27 @@ +Contributing +============ + +If you're submitting patches to pagure, please observe the following: + +- Check that your python code is `PEP8-compliant + `_. There is a `pep8 tool + `_ that can automatically check + your source. + +- Check that your code doesn't break the test suite. The test suite can be + run using the ``runtests.sh`` shell script at the top of the sources. + See :doc:`development` for more information about the test suite. + +- If you are adding new code, please write tests for them in ``tests/``, + the ``runtests.sh`` script will help you to see the coverage of your code + in unit-tests. + +- If your change warrants a modification to the docs in ``doc/`` or any + docstrings in ``pagure/`` please make that modification. + +.. note:: You have a doubt, you don't know how to do something, you have an + idea but don't know how to implement it, you just have something bugging + you? + + Come to see us on IRC: ``#fedora-apps`` on irc.freenode.net or directly on + `the project `_. diff --git a/doc/contributors.rst b/doc/contributors.rst new file mode 100644 index 0000000..309e530 --- /dev/null +++ b/doc/contributors.rst @@ -0,0 +1,75 @@ +Contributors to pagure +========================= + +Pagure would be nothing without its contributors. + +On July 27, 2016 (release 2.3.4), the list looks as follow: + +================= =========== +Number of commits Contributor +================= =========== + 4107 Pierre-Yves Chibon + 174 Ryan Lerch + 61 farhaanbukhsh + 59 Johan Cwiklinski + 48 Clement Verna + 36 Vivek Anand + 18 Sayan Chowdhury + 15 Gaurav Kumar + 15 Lubomír Sedlář + 15 Patrick Uiterwijk + 15 Ralph Bean + 13 Ghost-script + 13 Mathieu Bridon + 8 Lei Yang + 5 Mike McLean + 5 Oliver Gutierrez + 5 vanzhiganov + 5 yangl1996 + 4 Maciej Lasyk + 4 Paul W. Frields + 3 Ankush Behl + 3 Anthony Lackey + 3 Dhriti Shikhar + 3 Eric Barbour + 3 Jan Pokorný + 3 Kushal Khandelwal + 3 Pedro Lima + 2 Daniel Mach + 2 Nuno Maltez + 2 Richard Marko + 2 Ricky Elrod + 2 Simo Sorce + 2 Till Maas + 2 bruno + 2 dhrish20 + 1 Anthony Lackey + 1 David Caro + 1 Eric Barbour + 1 Kunaal Jain + 1 Mathew Robinson + 1 Pierre-YvesChibon + 1 Rahul Bajaj + 1 Stanislav Ochotnicky + 1 Vyacheslav Anzhiganov + 1 Yves Martin + 1 abhishek + 1 jcvicelli + 1 pingou + 1 ryanlerch + 1 skrzepto + 1 skrzepto + 1 tenstormavi +================= =========== + +This list is generated using + +:: + + git shortlog -s -n -e + + +The old pagure logo has been created by ``Micah Denn ``, +the new one, as well as the entire version 2 of the user interface (using +bootstrap) is the work of ``Ryan Lerch `` many thanks +to them for their work and understanding during the process. diff --git a/doc/development.rst b/doc/development.rst new file mode 100644 index 0000000..2fb8d7b --- /dev/null +++ b/doc/development.rst @@ -0,0 +1,261 @@ +Development +=========== + +Get the sources +--------------- + +Anonymous: + +:: + + git clone https://pagure.io/pagure.git + +Contributors: + +:: + + git clone ssh://git@pagure.io:pagure.git + + +Dependencies +------------ + +The dependencies of pagure are listed in the file ``requirements.txt`` +at the top level of the sources. + + +.. note:: working in a `virtualenv `_ + is tricky due to the dependency on `pygit2 `_ + and thus on `libgit2 `_ + but the pygit2 `documentation has a solution for this + `_. + + +Run pagure for development +-------------------------- +Adjust the configuration file (secret key, database URL, admin group...) +See :doc:`configuration` for more detailed information about the +configuration. + + +Create the database scheme:: + + ./createdb + +Create the folder that will receive the different git repositories: + +:: + + mkdir {repos,docs,forks,tickets,requests,remotes} + + +Run the server: + +:: + + ./runserver + +If you want to change some configuration key you can create a file, place +the configuration change in it and use it with + +:: + + ./runserver -c + +For example, create the file ``config`` with in it: + +:: + + from datetime import timedelta + # Makes the admin session longer + ADMIN_SESSION_LIFETIME = timedelta(minutes=20000000) + + # Use a postgresql database instead of sqlite + DB_URL = 'postgresql://user:pass@localhost/pagure' + # Change the OpenID endpoint + FAS_OPENID_ENDPOINT = 'https://id.stg.fedoraproject.org' + + APP_URL = '*' + EVENTSOURCE_SOURCE = 'http://localhost:8080' + EVENTSOURCE_PORT = '8080' + DOC_APP_URL = '*' + + # Avoid sending email when developping + EMAIL_SEND = False + +and run the server with: + +:: + + ./runserver -c config + +To get some profiling information you can also run it as: + +:: + + ./runserver.py --profile + + +You should be able to access the server at http://localhost:5000 + + +Every time you save a file, the project will be automatically restarted +so you can see your change immediatly. + + + +Create a pull-request for testing +---------------------------------- + +When working on pagure, it is pretty often that one wanted to work on a +feature or a bug related to pull-requests needs to create one. + +Making a pull-request for development purposes isn't hard, if you remember +that since you're running a local instance, the git repos created in your +pagure instance are also local. + +So here are in a few steps that one could perform to create a pull-request in a +local pagure instance. + +* Create a project on your pagure instance, let's say it will be called ``test`` + +* Create a folder ``clones`` somewhere in your system (you probably do not + want it in the ``repos`` folder created above, next to it is fine though):: + + mkdir clones + +* Clone the repo of the ``test`` project into this ``clones`` folder:: + + cd clones + git clone ~/path/to/pagure/repos/test.git + +* Add and commit some files:: + + echo "*~" > .gitignore + git add .gitignore + git commit -m "Add a .gitignore file" + echo "BSD" > LICENSE + git add LICENSE + git commit -m "Add a LICENSE file" + +* Push these changes:: + + git push -u origin master + +* Create a new branch and add a commit in it:: + + git branch new_branch + git checkout new_branch + touch test + git add test + git commit -m "Add file: test" + +* Push this new branch:: + + git push -u origin new_branch + + +Then go back to your pagure instance running in your web-browser, check the +``test`` project. You should see two branches: ``master`` and ``new_branch`` +from there you should be able to open a new pull-request, either from the +front page or via the ``File Pull Request`` button in the ``Pull Requests`` +page. + + + +Coding standards +---------------- + +We are trying to make the code `PEP8-compliant +`_. There is a `pep8 tool +`_ that can automatically check +your source. + + +We are also inspecting the code using `pylint +`_ and aim of course for a 10/10 code +(but it is an assymptotic goal). + +.. note:: both pep8 and pylint are available in Fedora via yum: + + :: + + yum install python-pep8 pylint + + +Send patch +---------- + +The easiest way to work on pagure is to make your own branch in git, make +your changes to this branch, commit whenever you want, rebase on master, +whenever you need and when you are done, send the patch either by email, +via the trac or a pull-request (using git or github). + + +The workflow would therefore be something like: + +:: + + git branch + git checkout + + git commit file1 file2 + + git commit file3 file4 + git checkout master + git pull + git checkout + git rebase master + git format-patch -2 + +This will create two patch files that you can send by email to submit in a ticket +on pagure, by email or after forking the project on pagure by submitting a +pull-request (in which case the last step above ``git format-patch -2`` is not +needed. + + +Unit-tests +---------- + +Pagure has a number of unit-tests. + + +We aim at having a full (100%) coverage of the whole code (including the +Flask application) and of course a smart coverage as in we want to check +that the functions work the way we want but also that they fail when we +expect it and the way we expect it. + + +Tests checking that function are failing when/how we want are as important +as tests checking they work the way they are intended to. + +``runtests.sh``, located at the top of the sources, helps to run the +unit-tests of the project with coverage information using `python-nose +`_. + + +.. note:: You can specify additional arguments to the nose command used + in this script by just passing arguments to the script. + + For example you can specify the ``-x`` / ``--stop`` argument: + `Stop running tests after the first error or failure` by just doing + + :: + + ./runtests.sh --stop + + +Each unit-tests files (located under ``tests/``) can be called +by alone, allowing easier debugging of the tests. For example: + +:: + + python tests/test_pragure_lib.py + + +.. note:: In order to have coverage information you might have to install + ``python-coverage`` + + :: + + yum install python-coverage diff --git a/doc/index.rst b/doc/index.rst new file mode 100644 index 0000000..268c3e3 --- /dev/null +++ b/doc/index.rst @@ -0,0 +1,51 @@ +Pagure +======= + +Pagure is a light-weight git-centered forge based on pygit2. + +Features: + +* ``Open-sources``: Web-interface for the git repositories +* ``Flexibility``: Ability to create any project you want +* ``One place``: Keep your documentation and tickets in pagure +* ``Collaboration``: Fork a project and make a pull-request +* ``Integration``: Create pull-request from a fork hosted somewhere else than in + pagure +* ``Open data``: Sources, doc, ticket and pull-requests meta-data are available + in the web interface but also in git repos which can thus be cloned and changed + locally. +* ``Freedom``: Pagure is fully Free and Open-Source Software! + + +Resources: + +- `Home page `_ +- `Git repository `_ +- `Github mirror `_ + + +Contents: + +.. toctree:: + :maxdepth: 2 + + overview + install + install_milter + install_evs + install_webhooks + configuration + development + usage + contributing + contributors + + + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` + diff --git a/doc/install.rst b/doc/install.rst new file mode 100644 index 0000000..7490087 --- /dev/null +++ b/doc/install.rst @@ -0,0 +1,264 @@ +Installing pagure +================= + +There are two ways to install pagure: + +* via the RPM package (recommanded if you are using a RPM-based linux distribution) +* via the setup.py + + + +Installing pagure via RPM +------------------------- + +Here as well there are two ways of obtaining the RPM: + +* From the main repositories + +Pagure is packaged for Fedora since Fedora 21 and is available for RHEL and +its derivative via the `EPEL repository `. +So installing it is as easy as: +:: + + dnf install pagure pagure-milters pagure-ev pagure-webhook + +or + +:: + + yum install pagure pagure-milters pagure-ev pagure-webhook + +The ``pagure`` package contains the core of the application and the doc server. +(See the ``Overview`` page for a global overview of the structure of the +project). + +The ``pagure-milters`` package contains, as the name says, the milter (a +mail filter to hook into a MTA). + +The ``pagure-ev`` package contains the eventsource server. + +The ``pagure-webhook`` package contains the web-hook server. + + +.. note:: The last three packages are optional, pagure would work fine without + them but the live-update, the webhook and the comment by email + services will not work. + +* From the sources + +If you wish to run a newer version of pagure than what is in the repositories +you can easily rebuild it as RPM. + +Simply follow these steps: +# Clone the sources:: + + git clone https://pagure.io/pagure.git + +# Go to the folder:: + + cd pagure + +# Build a tarball of the latest version of pagure:: + + python setup.py sdist + +# Build the RPM:: + + rpmbuild -ta dist/pagure*.tar.gz + +This will build pagure from the version present in your clone. + + +Once, the RPM is installed the services ``pagure_milter`` and ``pagure_ev`` +are ready to be used but the database and the web-application parts still +need to be configured. + + + +Installing pagure via setup.py +------------------------------ + +Pagure includes in its sources a ``setup.py`` automatint the installation +of the web applications of pagure (ie: the core + the doc server). + + +To install pagure via this mechanism simply follow these steps: +# Clone the sources:: + + git clone https://pagure.io/pagure.git + +# Go to the folder:: + + cd pagure + +# Install the latest version of pagure:: + + python setup.py build + sudo python setup.py install + +.. note:: To install the eventsource server or the milter, refer to their + respective documentations. + +# Install the additional files as follow: + ++------------------------------+------------------------------------------+ +| Source | Destination | ++=============================+===========================================+ +| ``files/pagure.cfg.sample`` | ``/etc/pagure/pagure.cfg`` | ++------------------------------+------------------------------------------+ +| ``files/alembic.ini`` | ``/etc/pagure/alembic.ini`` | ++------------------------------+------------------------------------------+ +| ``files/pagure.conf`` | ``/etc/httpd/conf.d/pagure.conf`` | ++------------------------------+------------------------------------------+ +| ``files/pagure.wsgi`` | ``/usr/share/pagure/pagure.wsgi`` | ++------------------------------+------------------------------------------+ +| ``createdb.py`` | ``/usr/share/pagure/pagure_createdb.py`` | ++------------------------------+------------------------------------------+ + + + +Set-up pagure +------------- + +Once pagure's files are installed, you still need to set up some things. + + +* Create the folder release + +This folder is used by project maintainers to upload the tarball of the +releases of their project. + +This folder must be accessible by the user under which the application is +running (in our case: ``git``). +:: + + mkdir -p /var/www/releases + chown git:git /var/www/releases + + +* Create the folders where the repos, forks and checkouts will be stored + +Pagure stores the sources of a project in a git repo, offers a place to +store the project's documentation in another repo, stores a JSON dump of all +issues and of all pull-requests in another two repos, and keeps a local +checkout of remote projects when asked to do remote pull-requests. +All these repositories are stored in different folders that must be +created manually. + +For example you can place them under ``/srv/git/repositories/`` which would +make ``/srv/git`` the home of your gitolite user. + +You would then create the folders with: +:: + + mkdir /srv/git/repositories/{docs,forks,tickets,requests,remotes} + + +* Configure apache + +If installed by RPM, you will find an example apache configuration file +at: ``/etc/httpd/conf.d/pagure.conf``. + +If not installed by RPM, the example files is present in the sources at: +``files/pagure.conf``. + +Adjust it for your needs. + + +* Configure the WSGI file + +If you installed by RPM, you will find an example WSGI file at: +``/usr/share/pagure/pagure.wsgi`` and ``/usr/share/pagure/docs_pagure.wsgi`` +for the doc server. + +If you did not install by RPM, these files are present in the sources at: +``files/pagure.wsgi`` and ``files/doc_pagure.wsgi``. + +Adjust them for your needs + + +* Give apache permission to read the repositories owned by the ``git`` user. + +For the sake of this document, we assume that the web application runs under +the ``git`` user, the same user as your gitolite user, but apache itself +runs under the ``httpd`` (or ``apache2``) user. So by default, apache +will not be allowed to read git repositories created and managed by gitolite. + +To give apache this permission (required to make git clone via http work), +we use file access control lists (aka FACL): +:: + + setfacl -m user:apache:rx --default + setfacl -Rdm user:apache:rx /srv/git + setfacl -Rm user:apache:rx /srv/git + +Where ``/srv/git`` is the home of your gitolite user (which will thus need +to be adjusted for your configuration). + + +* Set up the configuration file of pagure + +This is an important step which concerns the file ``/etc/pagure/pagure.cfg``. +If you have installed pagure by RPM, this file is already there, otherwise +you can find an example one in the sources at: ``files/pagure.cfg.sample`` +that you will have to copy to the right location. + +Confer the ``Configuration`` section of this documentation for a full +explanation of all the options of pagure. + +* Create the database + +You first need to create the database itself. For this, since pagure can +work with: `PostgreSQL `_, +`MySQL `_ or `MariaDB `_, we +would like to invite you to consult the documentation of your database system +for this operation. + +Once you have specified in the configuration file the to url used to connect +to the database, and create the database itself, you can now create the +tables, the database scheme. + +To create the database tables, you need to run the script +``/usr/share/pagure/pagure_createdb.py`` and specify it the configuration +file to use via an environment variable. + +For example: +:: + + PAGURE_CONFIG=/etc/pagure/pagure.cfg python /usr/share/pagure/pagure_createdb.py + +This will tell ``/usr/share/pagure/pagure_createdb.py`` to use the database +information specified in the file ``/etc/pagure/pagure.cfg``. + +.. warning:: Pagure's default configuration is using sqlite. This is fine + for development purpose but not for production use as sqlite does + not support all the operations needed when updating the database + schema. Do use PostgreSQL, MySQL or MariaDB in production. + +* Stamp the alembic revision + +For changes to existing tables, we rely on `Alembic `_. +It uses `revisions` to perform the upgrades, but to know which upgrades are +needed and which are already done, the current revision needs to be saved +in the database. This will allow alembic to know apply the new revision when +running it. + +You can save the current revision in the database using the following command: +:: + + cd /etc/pagure + alembic stamp $(alembic heads |awk '{ print $1 }') + +The ``cd /etc/pagure`` is needed as the command must be run in the folder +where the file ``alembic.ini`` is. This file contains two important pieces +of information: + +* ``sqlalchemy.url`` which is the URL used to connect to the database, likely +the same URL as the one in ``pagure.cfg``. +* ``script_location`` which is the path to the ``versions`` folder containing +all the alembic migration files. + +The ``alembic stamp`` command is the one actually saving the current revision +into the database. This current revision is found using ``alembic heads`` +which returns the most recent revision found by alembic, and since the +database was just created, it is at the latest revision. diff --git a/doc/install_evs.rst b/doc/install_evs.rst new file mode 100644 index 0000000..51f4965 --- /dev/null +++ b/doc/install_evs.rst @@ -0,0 +1,48 @@ +Installing pagure's EventSource server +====================================== + +Eventsource or Server Sent Events are messages sent from a server to a web +browser. It allows to refresh a page "live", ie, without the need to reload +it entirely. + + +Configure your system +--------------------- + +The eventsource server is easy to set-up. + +* Install the required dependencies + +:: + + python-redis + python-trollius + python-trollius-redis + +.. note:: We ship a systemd unit file for pagure_milter but we welcome patches + for scripts for other init systems. + + +* Install the files of the SSE server as follow: + ++----------------------------------------+-----------------------------------------------------+ +| Source | Destination | ++========================================+=====================================================+ +| ``ev-server/pagure-stream-server.py`` | ``/usr/libexec/pagure-ev/pagure-stream-server.py`` | ++----------------------------------------+-----------------------------------------------------+ +| ``ev-server/pagure_ev.service`` | ``/etc/systemd/system/pagure_ev.service`` | ++----------------------------------------+-----------------------------------------------------+ + +The first file is the script of the SSE server itself. + +The second file is the systemd service file. + + +* Finally, activate the service and ensure it's started upon boot: + +:: + + systemctl enable redis + systemctl start redis + systemctl enable pagure_ev + systemctl start pagure_ev diff --git a/doc/install_milter.rst b/doc/install_milter.rst new file mode 100644 index 0000000..dc1524f --- /dev/null +++ b/doc/install_milter.rst @@ -0,0 +1,79 @@ +Installing pagure's milter +========================== + +A milter is a script that is ran by a Mail Transfer Agent (`MTA +`_) +upon receiving an email via either a network or an unix socket. + +If you want more information feel free to check out the corresponding page +on wikipedia: `https://en.wikipedia.org/wiki/Milter +`_. + +Configure your system +--------------------- + +* Install the required dependencies + +:: + + python-pymilter + +.. note:: We ship a systemd unit file for pagure_milter but we welcome patches + for scripts for other init systems. + +.. note:: It also requires a MTA, we used postfix. + + +* Create an alias ``reply`` + +This can be done in ``/etc/aliases``, for example: +:: + + reply: /dev/null + + +* Activate the ability of your MTA, to split users based on the character ``+``. + This way all the emails sent to ``reply+...@example.com`` will be forwarded + to your alias for ``reply``. + + +In postfix this is done via: +:: + + recipient_delimiter = + + +* Hook the milter in the MTA + +In postfix this is done via: +:: + + non_smtpd_milters = unix:/var/run/pagure/paguresock + smtpd_milters = unix:/var/run/pagure/paguresock + + +* Install the files of the milter as follow: + ++--------------------------------------+---------------------------------------------------+ +| Source | Destination | ++======================================+===================================================+ +| ``milters/comment_email_milter.py`` | ``/usr/share//pagure/comment_email_milter.py`` | ++--------------------------------------+---------------------------------------------------+ +| ``milters/milter_tempfile.conf`` | ``/usr/lib/tmpfiles.d/pagure-milter.conf`` | ++--------------------------------------+---------------------------------------------------+ +| ``milters/pagure_milter.service`` | ``/etc/systemd/system/pagure_milter.service`` | ++--------------------------------------+---------------------------------------------------+ + +The first file is the script of the milter itself. + +The second file is a file specific for systemd and ensuring the temporary +folders needed by the milter are re-created if needed at each boot. + +The third file is the systemd service file. + + +* Activate the service and ensure it's started upon boot: + +:: + + systemctl enable pagure_milter + systemctl start pagure_milter diff --git a/doc/install_webhooks.rst b/doc/install_webhooks.rst new file mode 100644 index 0000000..2aec5e6 --- /dev/null +++ b/doc/install_webhooks.rst @@ -0,0 +1,49 @@ +Installing pagure's web-hooks notification system +================================================= + +Web-hooks are a notification system upon which a system makes a http POST +request with some data upon doing an action. This allows notifying a system +that an action has occured. + +If you want more information feel free to check out the corresponding page +on wikipedia: `https://en.wikipedia.org/wiki/Webhook +`_. + +Configure your system +--------------------- + +* Install the required dependencies + +:: + + python-redis + python-trollius + python-trollius-redis + +.. note:: We ship a systemd unit file for pagure_webhook but we welcome patches + for scripts for other init systems. + + +* Install the files of the web-hook server as follow: + ++----------------------------------------------+----------------------------------------------------------+ +| Source | Destination | ++==============================================+==========================================================+ +| ``webhook-server/pagure-webhook-server.py`` | ``/usr/libexec/pagure-webhook/pagure-webhook-server.py`` | ++----------------------------------------------+----------------------------------------------------------+ +| ``webhook-server/pagure_webhook.service`` | ``/etc/systemd/system/pagure_webhook.service`` | ++----------------------------------------------+----------------------------------------------------------+ + +The first file is the script of the web-hook server itself. + +The second file is the systemd service file. + + +* Activate the service and ensure it's started upon boot: + +:: + + systemctl enable redis + systemctl start redis + systemctl enable pagure_webhook + systemctl start pagure_webhook diff --git a/doc/milter.rst b/doc/milter.rst new file mode 100644 index 0000000..5ab8ec2 --- /dev/null +++ b/doc/milter.rst @@ -0,0 +1,62 @@ +Pagure's Milter +=============== + +`Milter `_ are script executed by +postfix upon sending or receiving an email. + +We use this system to allow pagure's users to comment on a ticket (or a +pull-request) by directly replying to the email sent as a notification. + +Pagure's milter is designed to be run on the same machine as the mail server +(postfix by default). Postfix connecting to the milter via a unix socket. + +The milter itself is a service managed by systemd. +You can find all the relevant files for the milter under the ``milters`` folder +in the sources. + + +Install the milter +------------------ + +The first step to enable the milter on a pagure instance is thus to install the +``.service`` file for systemd and place the corresponding script that, by +default, should go to ``/usr/share/pagure/comment_email_milter.py``. + +If you are using the RPM, install ``pagure-milters`` should provide and install +all the files correctly. + + +Activate the milter +------------------- + +Make sure the milter is running and will be automaticall started at boot by +running the commands: + +To start the milter: + +:: + + systemctl start pagure_milter + +To ensure the milter is always started at boot time: + +:: + + systemctl enable pagure_milter + + +Activate the milter in postfix +------------------------------ + +To actually activate the milter in postfix is in fact really easy, all it takes +is two lines in the ``main.cf`` file of postfix: + +:: + + non_smtpd_milters = unix:/var/run/pagure/paguresock + smtpd_milters = unix:/var/run/pagure/paguresock + +These two lines are pointing to the unix socket used by postfix to communicate +with the milter. This socket is defined in the milter file itself, in the +sources: ``milters/comment_email_milter.py``. + diff --git a/doc/overview.ascii b/doc/overview.ascii new file mode 100644 index 0000000..3c003cd --- /dev/null +++ b/doc/overview.ascii @@ -0,0 +1,53 @@ + Grants/Denies access + +------------+ +-----------+ + | | | | + User's git actions+---------------------->| Gitolite +-------------------------->+ Git repos | + | | | | + +-----+------+ +-----------+ + ^ + | + | + +------------------------------------------+ + | + | + +-------------------------+ | + Notifications | | | + +------------------------------------+ Postfix |<--------------------------------+ + | | | | | + | | +-------------------+ | | + | | | | | | + v | | Pagure's milter | | | + User's mail client | | +--------------+ | | + + +-----+--------+----------+ | | | + | ^ Updates | | | + | | | | | + | Replies | | | | + +---------------------------------------------------+ | | | + | | | + | | | + +--------------+ | | | + | | | | | + +----------------------->| Pagure | v | | + | | Doc server | +------------+-+ | + | | | |{s} | | + | +--------------+ +------->| Database | | + | | | | | + User's web browser--+ http requests Updates | +--------------+ | + ^ | & queries| | + | | | | + | | +--------------+ | | + | | | +----------+---------------------------------+ + | +----------------------->| Pagure | + | | web server +---+ +----------------------+ +----------------+ + | | | | | | | | + | +--------------+ | | Pagure | | Third Party | + | +---------->| Web hooks' server +-------------->| Services | + | | | | | | + | redis | +----------------------+ +----------------+ + | | + | | +----------------------+ + | +---------->| | + | | Pagure | + +----------------------------------------------------------------------+ EventSource server | + Server-Sent Event | | + +----------------------+ diff --git a/doc/overview.rst b/doc/overview.rst new file mode 100644 index 0000000..cc4e06b --- /dev/null +++ b/doc/overview.rst @@ -0,0 +1,104 @@ +Overview +======== + +Pagure is split over multiple components, each having their purpose and all +but one (the core application) being optional. + +These components are: + +.. contents:: + + +Before going into the overall picture, one should realize that most of the +components listed above are optional. + +Here is a diagram representing pagure without all the optionals components: + +.. image:: _static/overview_simple.png + :target: _static/overview_simple.png + + +And here is a diagram of all the components together: + +.. image:: _static/overview.png + :target: _static/overview.png + +Pagure core application +----------------------- + +The core application is the flask application interacting with gitolite to +provide a web UI to the git repositories as well as tickets and pull-requests. +This is the main application for the forge. + + +Gitolite +-------- + +Currently pagure uses `gitolite `_ +to grant or deny `ssh `_ access +to the git repositories, in other words to grant or deny read and/or write +access to the git repositories. + +Pagure supports cloning over both ssh and http, but writing can only be done +via ssh, through gitolite. + + +Pagure doc server +----------------- + +While integrated into the main application at first, it has been split out +for security concern, displaying information directly provided by the user +without a clear/safe way of filtering for un-safe script or hacks is a +security hole. +For this reason we also strongly encourage anyone wanting to deploy their +own instance of pagure with the doc server, to run this application on a +completely different domain name (not just a sub-domain) in order to reduce +the cross-site forgery risks. + +Pagure can be run just fine without the doc server, all you need to do is to +**not** define the variable ``DOC_APP_URL`` in the configuration file. + + +Pagure milter +------------- + +The milter is a script, receiving an email as input and performing an action +with it. + +In the case of pagure, the milter is used to allow replying on a comment +of a ticket or a pull-request by directly replying to the notification sent. +No need to go to the page anymore to reply to a comment someone made. + +The milter integrates with a MTA such as postfix or sendmail that you will +have running and have access to in order to change its configuration. + + +Pagure EventSource Server +------------------------- + +Eventsource or Server Sent Events are messages sent from a server to a browser. + +For pagure this technology is used to allow live-refreshing of a page when +someone is viewing it. For example, while you are reading a ticket if someone +comments on it, the comment will automatically show up on the page without +the need for you to reload the entire page. + +The flow is: the main pagure server does an action, sends a message over +redis, the eventsource server picks it up and send it to the browsers waiting +for it, then javascript code is executed to refresh the page based on the +information received. + + +Pagure web-hook Server +------------------------- + +Sends notifications to third party services using POST http requests. + +This is the second notifications system in pagure with `fedmsg `_. +These notifications are running on their own service to prevent blocking the +main web application in case the third part service is timing-out or just +being slow. + +The flow is: the main pagure server does an action, sends a message over +redis, the web-hook server picks it up, build the query and performs the +POST request to the specified URLs. diff --git a/doc/overview_simple.ascii b/doc/overview_simple.ascii new file mode 100644 index 0000000..a1a129b --- /dev/null +++ b/doc/overview_simple.ascii @@ -0,0 +1,26 @@ + + Grants/Denies access + +------------+ +-----------+ + |cfffca4 | | | +User's git actions+--------------------->+ Gitolite +-------------------------->+ Git repos | + | | | | + +------------+ +-----------+ + ^ + | + +-----------------------------+ + | +User's mail client | + ^ +---------------+ | + | Notifications | | | + +----------------------------------+ Mail server | | + | | | + +---------------+ | + ^ | + | | + | | + +--------------+ Updates +--------------+ + | | & queries |{s} | +User's web browser+---------------------->+ Pagure +------------->| Database | + | web server | | | + | | +--------------+ + +--------------+ diff --git a/doc/requirements.txt b/doc/requirements.txt new file mode 100644 index 0000000..c577a66 --- /dev/null +++ b/doc/requirements.txt @@ -0,0 +1,2 @@ +sphinx +cloud_sptheme diff --git a/doc/usage.rst b/doc/usage.rst new file mode 100644 index 0000000..aee0095 --- /dev/null +++ b/doc/usage.rst @@ -0,0 +1,43 @@ +Usage +===== + +Using pagure should come fairly easily, especially to people already used +to forges such as GitHub or GitLab. There are however some tips and tricks +which can be useful to know and that this section of the doc covers. + + +One of the major difference with GitHub and GitLab is that for each project +on pagure, four git repositories are made available to the admins of the +project: + +* A git repository containing the source code, displayed in the main section + of the pagure project. +* A git repository for the documentation +* A git repository for the issues and their metadata +* A git repository for the metadata for pull-requests + + +You can find the URLs to access or clone these git repositories on the +overview page of the project. On the menu on the right side, there is a menu +`Source GIT URLs`, next to it is a little `more` button, by clicking on it +you will be given the URLs to the other three git repos. + +Each section correspond to one of the four git repositories created for each +project. + + + +Contents: + +.. toctree:: + :maxdepth: 2 + + usage/first_steps + usage/project_settings + usage/roadmap + usage/using_doc + usage/using_webhooks + usage/ticket_templates + usage/pr_custom_page + usage/theming + usage/upgrade_db diff --git a/doc/usage/_static/pagure_custom_pr.png b/doc/usage/_static/pagure_custom_pr.png new file mode 100644 index 0000000..5086d23 Binary files /dev/null and b/doc/usage/_static/pagure_custom_pr.png differ diff --git a/doc/usage/_static/pagure_my_settings.png b/doc/usage/_static/pagure_my_settings.png new file mode 100644 index 0000000..d7fb5c7 Binary files /dev/null and b/doc/usage/_static/pagure_my_settings.png differ diff --git a/doc/usage/_static/pagure_roadmap2.png b/doc/usage/_static/pagure_roadmap2.png new file mode 100644 index 0000000..a08114f Binary files /dev/null and b/doc/usage/_static/pagure_roadmap2.png differ diff --git a/doc/usage/_static/pagure_ticket_template.png b/doc/usage/_static/pagure_ticket_template.png new file mode 100644 index 0000000..3153907 Binary files /dev/null and b/doc/usage/_static/pagure_ticket_template.png differ diff --git a/doc/usage/first_steps.rst b/doc/usage/first_steps.rst new file mode 100644 index 0000000..cdb42e4 --- /dev/null +++ b/doc/usage/first_steps.rst @@ -0,0 +1,80 @@ +First Steps on pagure +===================== + +When coming to pagure for the first time there are a few things one should +do or check to ensure all works as desired. + +Login to pagure or create your account +-------------------------------------- + +Pagure has its own user account system. + +For instances of pagure such as the one at `pagure.io `_ +where the authentication is delegated to a third party (in the case of +pagure.io, the Fedora Account System) via OpenID, the local user account +is created upon login. + +This means, you cannot be added to a group or a project before you login for +the first time as the system will simply not know you. + +If you run your own pagure instance which uses the local authentication +system, then you will find on the login page an option to create a new +account. + + +Upload your SSH key +------------------- + +Pagure uses gitolite to manage who has read/write access to which git +repository via `ssh `_. + +An ssh key is composed of two parts: + +* a private key, which you must keep to yourself and never share with anyone. +* a public key, which is public and therefore can be shared with anyone. + +If you have never generated a ssh key, you can do so by running: + +:: + + ssh-keygen + +or alternatively on GNOME using the application ``seahorse``. + +This will create two files in ``~/.ssh/`` (``~`` is the symbol for your home +folder). + +These two files will be named (for example) ``id_rsa`` and ``id_rsa.pub``. +The first one is the private key that must never be shared. The second is +the public key that can be uploaded on pagure to give you ssh access. + +To upload your public key onto pagure, login and click on the user icon on +the top right corner, there, select ``My settings``. + +.. image:: _static/pagure_my_settings.png + :target: _static/pagure_my_settings.png + + +Configure the default email address +----------------------------------- + +If the pagure instance you use is using local user authentication, then when +you created your account you could choose whichever email address you prefer +to use, but in the case (like pagure.io) where the pagure instance relies +on an external authentication service, the email address provided by this +service may be different from the one you prefer. + +Your settings' page (cf the image above for how to access to the page) allow +you to add multiple email address and set one as default. + +Your default email address is the address that will be used to send you +notifications and also as the email address in the git commit if you merge +a pull-request with a merge commit. + +For online editing, when doing the commit, you will be presented with the +list of valid email addresses associated with your account and you will be +able to choose which one you wish to use. + +.. note:: All email address will need to be confirmed to be activated, this + is done via a link sent by email to the address. If you do not + receive this link, don't forget to check your spam folder! diff --git a/doc/usage/pr_custom_page.rst b/doc/usage/pr_custom_page.rst new file mode 100644 index 0000000..2fdeb5e --- /dev/null +++ b/doc/usage/pr_custom_page.rst @@ -0,0 +1,74 @@ +Customize the PR page +===================== + +Pagure offers the possibility to customize the page that creates pull-request +to add your specific information, such as: please follow the XYZ coding style, +run the tests or whatever you wish to inform contributors when they open a +new pull-request. + +The customization is done via a file in the git repository containing the +meta-data for the pull-requests. This file must be placed under a ``templates`` +folder, be named ``contributing.md`` and can be formatted as you wish using +markdown. + + +Example +------- + +For a project named ``test`` on ``pagure.io``. + +* First, clone the pull-request git repo [#f1]_ and move into it + +:: + + git clone ssh://git@pagure.io/requests/test.git + cd test + +* Create the templates folder + +:: + + mkdir templates + +* Create the customized PR info + +:: + + vim templates/contributing.md + +And place in this file the following content: + +:: + + Contributing to test + ==================== + + When creating a pull-request against test, there are couple of items to do + that will speed up the review process: + + * Ensure the unit-tests are all passing (cf the ``runtests.sh`` script at the + top level of the sources) + * Check if your changes are [pep8](https://www.python.org/dev/peps/pep-0008/) + compliant for this you can install ``python-pep8`` and run the ``pep8`` CLI + tool + + +* Commit and push the changes to the git repo + +:: + + git add templates + git commit -m "Customize the PR page" + git push + + +* And this is how it will look like + +.. image:: _static/pagure_custom_pr.png + :target: _static/pagure_custom_pr.png + + + +.. [#f1] All the URLs to the different git repositories can be found on the + main page of the project, on the right-side menu, under the section + ``Source GIT URLs``, click on ``more`` to see them. diff --git a/doc/usage/project_settings.rst b/doc/usage/project_settings.rst new file mode 100644 index 0000000..2350a0c --- /dev/null +++ b/doc/usage/project_settings.rst @@ -0,0 +1,145 @@ +Project settings +================ + +Each project have a number of options that can be tweaked in the settings +page of the project which is accessible to the person having full commits +to the project. + +This page presents the different settings and there effect. + + +`Activate always merge` +------------------------ + +This boolean enables or disables always making a merge commit when merging +a pull-request. + +When merging a pull-request in pagure there are three states: + +* fast-forward: when the commits in the pull-request can be fast-forwarded +pagure signals it and just fast-forward the commit, keeping the history linear. +* merge: when the commits in the pull-request cannot be merged without a merge +commit, pagure signals it and performs this merge commit. +* conflicts: when the commits in the pull-request cannot be merged at all +automatically due to one or more conflicts. Then pagure signals it and prevent +merging. + +If the `Activate always merge` option is on, then the `fast-forward` option +above is disabled in favor of the `merge` option. + + +`Activate comment editing` +-------------------------- + +This boolean enables or disables editing comments. + +After commenting on a ticket or a pull-request, the admins of the project +and the author of the comment may be allowed to edit the comment. +This allows them to adjust the wording or the style as they wish. + +.. note:: notification about a comment is only sent once with the original + text, changes performed later will not trigger a new notification. + +Some project may not want to allow editing comments after they were posted +and this setting allows turning it on or off. + + +`Activate Enforce signed-off commits in pull-request` +----------------------------------------------------- + +This boolean enables or disables checking for a 'Signed-off-by' line (case +insensitive) in the commit messages of the pull-requests. + +If this line is missing, pagure will display a message near the `Merge` +button, allowing project admin to request the PR to be updated. + +.. note:: This setting does not prevent commits without this 'signed-off-by' + line to be pushed directly, it only work at the pull-request level. + + +`Activate issue tracker` +------------------------ + +This boolean simply enables or disables the issue tracker for the project. +So if you are tracking your ticket on a different system, you can simply +disable reporting issue on pagure by un-checking this option. + + +`Activate Minimum score to merge pull-request` +---------------------------------------------- + +This option can be used for project wishing to enforce having a minimum +number of people reviewing a pull-request before it can be merged. + +If this option is enabled, anyone can vote in favor or against a pull-request +and the sum of the votes in favor minus the sum of the votes againsts give +the pull-request a score that should be equal or great to the value +entered in this option for the pull-request to be allowed to be merged. + +.. note:: Only the main comments (ie: not in-line) are taken into account + to calculate the score of the pull-request. + +To vote in favor of a pull-request, use either: +* ``+1`` +* ``:thumbsup:`` + +To vote against a pull-request, use either: +* ``-1`` +* ``:thumbsdown:`` + +.. note:: Pull-Request reaching the minimum score are not automatically merged + +.. note:: Anyone can vote on the pull-request, not only the contributors. + + +`Activate Only assignee can merge pull-request` +----------------------------------------------- + +This option can be used for project wishing to institute a strong review +workflow where pull-request are first assigned then merged. + +If this option is enabled, only the person assigned to the pull-request +can merge it. + + +`Activate project documentation` +-------------------------------- + +Pagure offers the option to have a git repository specific for the +documentation of the project. + +This repository is then accessible under the ``Docs`` tab in the menu of the +project. + +If you prefer to store your documentation elsewhere or maybe even within +the sources of the project, you can disable the ``Docs`` tab by un-checking +this option. + + +`Activate pull requests` +------------------------ + +Pagure offers the option to fork a project, make changes to it and then ask +the developer to merge these changes into the project. This is similar to +the pull-request mechanism on GitHub or GitLab. + +However, some projects may prefer receiving patches by email on their list +or via another hosting plateform or simply do not wish to use the +pull-request mechanism at all. Un-checking this option will therefore +prevent anyone from opening a pull-request against this project. + +.. note:: disabling pull-requests does *not* disable forking the projects. + + +`Activate Web-hooks` +-------------------- + +Pagure offers the option of sending notification about event happening on a +project via [web-hooks|https://en.wikipedia.org/wiki/Webhook]. This option +is off by default and can be turned on for a pagure instance in its +configuration file. + +The URL of the web-hooks can be entered in this field. + +.. note:: See the ``notifications`` documentation to learn more about + web-hooks in pagure and how to use them. diff --git a/doc/usage/roadmap.rst b/doc/usage/roadmap.rst new file mode 100644 index 0000000..e1b7cd5 --- /dev/null +++ b/doc/usage/roadmap.rst @@ -0,0 +1,35 @@ +Using the roadmap feature +========================= + +Pagure allows building the roadmap of the project using the tickets and +their tags. + +The principal is as follow: + +* All the ticket with the tag ``roadmap`` will show up on the roadmap page. +* For each milestones defined in the settings of the project, the roadmap +will group tickets with the corresponding tag. +* Tickets with the tag ``roadmap`` that are not associated with any of the +milestones defined in the settings are group in an ``unplanned`` section. + + +Example +------- + +For a project named ``test`` on ``pagure.io``. + + + +* First, go to the settings page of the project, create the milestones you +like, for example: ``v1.0`` and ``v2.0``. + +* For the tickets you want to be on these milestones, go through each of them +and add them the tags: ``roadmap`` in combination with the milestone you want +``v1.0`` or ``v2.0``, or none of them if the ticket is on the roadmap but +not assigned to any milestones. + + +* And this is how it will look like + +.. image:: _static/pagure_roadmap2.png + :target: _static/pagure_roadmap2.png diff --git a/doc/usage/theming.rst b/doc/usage/theming.rst new file mode 100644 index 0000000..961b4ab --- /dev/null +++ b/doc/usage/theming.rst @@ -0,0 +1,81 @@ +Theme your pagure +================= + +Pagure via `flask-multistatic `_ +offers the possibility to override the default theme allowing to customize +the style of your instance. + +By default pagure looks for its templates and static files in the folders +``pagure/templates`` and ``pagure/static``, but you can ask pagure to look +for templates and static files in another folder. + +By specifying the configuration keys ``THEME_TEMPLATE_FOLDER`` and +``THEME_STATIC_FOLDER`` in pagure's configuration file, you tell pagure to +look for templates and static files first in these folders, then in its +usual folders. + + +.. note: The principal is that pagure will look in the folder specified in + the configuration file first and then in its usual folder, so the + **file names must be identical**. + +Example +------- + +Let's take an example, you wish to replace the pagure logo at the top right +of all the pages. + +This logo is part of the ``master.html`` template which all pages inherit +from. So what you want to do is replace this ``master.html`` by your own. + +* First, create the folder where your templates and static files will be stored: + +:: + + mkdir /var/www/mypaguretheme/templates + mkdir /var/www/mypaguretheme/static + +* Place your own logo in the static folder + +:: + + cp /path/to/your/my-logo.png /var/www/mypaguretheme/static + +* Place in there the original ``master.html`` + +:: + + cp /path/to/original/pagure/templates/master.html /var/www/mypaguretheme/templates + +* Edit it and replace the url pointing to the pagure logo (around line 27) + +:: + + - `_ +is provided as part of the sources of `flask-multistatic `_. diff --git a/doc/usage/ticket_templates.rst b/doc/usage/ticket_templates.rst new file mode 100644 index 0000000..9c45651 --- /dev/null +++ b/doc/usage/ticket_templates.rst @@ -0,0 +1,77 @@ +Templates for ticket input +========================== + +Pagure offers the possibility to add templates for ticket's input. These +templates do not enforce anything, users will have the possibility to simply +ignore it, or even to not follow it, but it also helps structuring the +ticket opened against a project and highlighting the information that are +often requested/needed. + +The templates are provided in the git repository containing the meta-data +for the tickets. +They must be placed under a ``templates`` folder in this git repository, +end with ``.md``and as the extension suggests can be formated as markdown. + +If you create a template ``templates/default.md``, it will be shown by +default when someone ask to create a new ticket. + + + +Example +------- + +For a project named ``test`` on ``pagure.io``. + +* First, clone the ticket git repo [#f1]_ and move into it + +:: + + git clone ssh://git@pagure.io/tickets/pagure.git + cd test + +* Create the templates folder + +:: + + mkdir templates + +* Create a default template + +:: + + vim templates/default.md + +And place in this file the following content: + +:: + + ##### Issue + + ##### Steps to reproduce + 1. + 2. + 3. + + ##### Actual results + + ##### Expected results + +* Commit and push the changes to the git repo + +:: + + git add templates + git commit -m "Add a default template for tickets" + git push + + +* And this is how it will look like + +.. image:: _static/pagure_ticket_template.png + :target: _static/pagure_ticket_template.png + + + +.. [#f1] All the URLs to the different git repositories can be found on the + main page of the project, on the right-side menu, under the section + ``Source GIT URLs``, click on ``more`` to see them. diff --git a/doc/usage/upgrade_db.rst b/doc/usage/upgrade_db.rst new file mode 100644 index 0000000..6c3ebe3 --- /dev/null +++ b/doc/usage/upgrade_db.rst @@ -0,0 +1,48 @@ +Upgrade a database +================== + + +Database schema migration are handled in two ways: + +* New tables + +For this we simply rely on the ``createdb`` script used when creating the +database the first time. + +* Changes to existing tables + +For changes to existing tables, we rely on `Alembic `_. +This allows us to do upgrade and downgrade of schema migration, kind of like +one would do commits in a system like git. + +To upgrade the database to the latest version simply run: +:: + + alembic upgrade head + +This may fail for different reasons: + +* The change was already made in the database + +This can be because the version of the database schema saved is incorrect. +It can be debugged using the following commands: + + * Find the current revision: ``alembic current`` + * See the entire history: ``alembic history`` + +Once the revision at which your database should be is found (in the history) +you can declare that your database is at this given revision using: +``alembic stamp ``. + +Eventually, if you do not know where your database is or should be, you can +do an iterative process stamping the database for every revision, one by one +trying everytime to ``alembic upgrade`` until it works. + +* The database used does not support some of the changes + +SQLite is handy for development but does not support all the features of a +real database server. Upgrading a SQLite database might therefore not work, +depending on the changes done. + +In some cases, if you are using a SQLite database, you will have to destroy +it and create a new one. diff --git a/doc/usage/using_doc.rst b/doc/usage/using_doc.rst new file mode 100644 index 0000000..d56535d --- /dev/null +++ b/doc/usage/using_doc.rst @@ -0,0 +1,108 @@ +Using the doc repository of your project +======================================== + +In this section of the documentation, we are interested in the doc repository. + +The doc repository is a simple git repo, whose content will appear under the +`Docs` tab in pagure and on https://docs.pagure.org//. + +There are a few ways you can put your documentation in this repo: + +* Simple text files + +Pagure will display them as plain text. If one of these is named ``index`` +it will be presented as the front page. + +* rst or markdown files + +Pagure will convert them to html on the fly and display them as such. +The rst files must end with `.rst` and the markdown ones must end with +``.mk``, ``.md`` or simply ``.markdown``. + +* html files + +Pagure will simply show them as such. + + +.. note: By default the `Docs` tab in the project's menu is disabled, you + will have to visit the project's settings page and turn it on + in the ``Project options`` section. + + +Example +------- + +Pagure's documentation is kept in pagure's sources, in the `doc` folder there. +You can see it at: `https://pagure.io/pagure/blob/master/f/doc +`_. This doc can be built with +`sphinx `_ to make it html and prettier. + +The built documentation is available at: `https://docs.pagure.org/pagure/ +`_. + +This is how it is built/updated: + +* Clone pagure's sources:: + + git clone https://pagure.io/docs/pagure.git + +* Move into its doc folder:: + + cd pagure/doc + +* Build the doc:: + + make html + +* Clone pagure's doc repository:: + + git clone ssh://git@pagure.io/docs/pagure.git + +* Copy the result of sphinx's build to the doc repo:: + + cp -r _build/html/* pagure/ + +* Go into the doc repo and update it:: + + cd pagure + git add . + git commit -am "Update documentation" + git push + +* Clean the sources:: + + cd .. + rm -rf pagure # remove the doc repo + rm -rf _build # remove the output from the sphinx's build + + +To make things simpler, the following script (name `update_doc.sh`) can be +used: + +:: + + #!/bin/bash + + make html + + git clone "ssh://git@pagure.io/docs/$1.git" + cp -r _build/html/* $1/ + ( + cd $1 + git add . + git commit -av + git push + ) + + rm -rfI _build + rm -rfI $1 + +It can be used by running `update_doc.sh ` from within the folder +containing the doc. + +So for pagure it would be something like: + +:: + + cd pagure/doc + update_doc.sh pagure diff --git a/doc/usage/using_webhooks.rst b/doc/usage/using_webhooks.rst new file mode 100644 index 0000000..7e48058 --- /dev/null +++ b/doc/usage/using_webhooks.rst @@ -0,0 +1,57 @@ +Using web-hooks +=============== + +Web-hooks are a notification system that could be compared to a callback. +Basically, pagure will make a HTTP POST request to one or more third party +server/application with information about what is or just happened. + +To set-up a web-hook, simply go to the settings page of your project and +enter the URL to the server/endpoint that will receive the notifications. + +There is, in the settings page, a web-hook key which is used by the +server (here pagure) to sign the message sent and which you can use to +ensure the notifications received are coming from the right source. + +Each POST request made contains two specific headers: + +:: + + X-Pagure-Topic + X-Pagure-Signature + + +``X-Pagure-Topic`` is a global header giving a clue about the type of action +that just occured. For example ``issue.edit``. + + +``X-Pagure-Signature`` contains the signature of the message allowing to +check that the message comes from pagure. + +.. warning:: These headers are present for convenience only, they are not + signed and therefore should not be trusted. Rely on the payload + after checking the signature to make any decision. + +Pagure relies on ``hmac`` to sign the content of its messages. If you want +to validate the message, in python, you can do something like the following: + +:: + + import hmac + import hashlib + + payload = # content you received in the POST request + headers = # headers of the POST request + project_web_hook_key = # private web-hook key of the project + + hashhex = hmac.new( + str(project_web_hook_key), payload, hashlib.sha1).hexdigest() + + if hashhex != headers.get('X-Pagure-Signature'): + raise Exception('Message received with an invalid signature') + + +The notifications sent via web-hooks have the same payload as what is sent +via `fedmsg `_. Therefore, the list of +pagure topics as well as example messages can be found in the +`fedmsg documentation about pagure +`_ diff --git a/ev-server/pagure-stream-server.py b/ev-server/pagure-stream-server.py new file mode 100644 index 0000000..746de60 --- /dev/null +++ b/ev-server/pagure-stream-server.py @@ -0,0 +1,248 @@ +#!/usr/bin/env python + +""" + (c) 2015 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + + +Streaming server for pagure's eventsource feature +This server takes messages sent to redis and publish them at the specified +endpoint + +To test, run this script and in another terminal +nc localhost 8080 + HELLO + + GET /test/issue/26?foo=bar HTTP/1.1 + +""" + +import datetime +import logging +import os +import urlparse + +import trollius +import trollius_redis + +log = logging.getLogger(__name__) + + +if 'PAGURE_CONFIG' not in os.environ \ + and os.path.exists('/etc/pagure/pagure.cfg'): + print 'Using configuration file `/etc/pagure/pagure.cfg`' + os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' + + +import pagure +import pagure.lib +from pagure.exceptions import PagureEvException + +SERVER = None + +def get_obj_from_path(path): + """ Return the Ticket or Request object based on the path provided. + """ + username = None + try: + if path.startswith('/fork'): + username, repo, obj, objid = path.split('/')[2:6] + else: + repo, obj, objid = path.split('/')[1:4] + except: + raise PagureEvException("Invalid URL: %s" % path) + + repo = pagure.lib.get_project(pagure.SESSION, repo, user=username) + + if repo is None: + raise PagureEvException("Project '%s' not found" % repo) + + output = None + if obj == 'issue': + if not repo.settings.get('issue_tracker', True): + raise PagureEvException("No issue tracker found for this project") + + output = pagure.lib.search_issues( + pagure.SESSION, repo, issueid=objid) + + if output is None or output.project != repo: + raise PagureEvException("Issue '%s' not found" % objid) + + if output.private: + # TODO: find a way to do auth + raise PagureEvException( + "This issue is private and you are not allowed to view it") + elif obj == 'pull-request': + if not repo.settings.get('pull_requests', True): + raise PagureEvException( + "No pull-request tracker found for this project") + + output = pagure.lib.search_pull_requests( + pagure.SESSION, project_id=repo.id, requestid=objid) + + if output is None or output.project != repo: + raise PagureEvException("Pull-Request '%s' not found" % objid) + + else: + raise PagureEvException("Invalid object provided: '%s'" % obj) + + return output + + +@trollius.coroutine +def handle_client(client_reader, client_writer): + data = None + while True: + # give client a chance to respond, timeout after 10 seconds + line = yield trollius.From(trollius.wait_for( + client_reader.readline(), + timeout=10.0)) + if not line.decode().strip(): + break + line = line.decode().rstrip() + if data is None: + data = line + + if data is None: + log.warning("Expected ticket uid, received None") + return + + data = data.decode().rstrip().split() + log.info("Received %s", data) + if not data: + log.warning("No URL provided: %s" % data) + return + + if not '/' in data[1]: + log.warning("Invalid URL provided: %s" % data[1]) + return + + url = urlparse.urlsplit(data[1]) + + try: + obj = get_obj_from_path(url.path) + except PagureEvException as err: + log.warning(err.message) + return + + origin = pagure.APP.config.get('APP_URL') + if origin.endswith('/'): + origin = origin[:-1] + + client_writer.write(( + "HTTP/1.0 200 OK\n" + "Content-Type: text/event-stream\n" + "Cache: nocache\n" + "Connection: keep-alive\n" + "Access-Control-Allow-Origin: %s\n\n" % origin + ).encode()) + + connection = yield trollius.From(trollius_redis.Connection.create( + host=pagure.APP.config['REDIS_HOST'], + port=pagure.APP.config['REDIS_PORT'], + db=pagure.APP.config['REDIS_DB'])) + + try: + + # Create subscriber. + subscriber = yield trollius.From(connection.start_subscribe()) + + # Subscribe to channel. + yield trollius.From(subscriber.subscribe(['pagure.%s' % obj.uid])) + + # Inside a while loop, wait for incoming events. + while True: + reply = yield trollius.From(subscriber.next_published()) + #print(u'Received: ', repr(reply.value), u'on channel', reply.channel) + log.info(reply) + log.info("Sending %s", reply.value) + client_writer.write(('data: %s\n\n' % reply.value).encode()) + yield trollius.From(client_writer.drain()) + + except trollius.ConnectionResetError as err: + log.exception("ERROR: ConnectionResetError in handle_client") + except Exception as err: + log.exception("ERROR: Exception in handle_client") + finally: + # Wathever happens, close the connection. + connection.close() + client_writer.close() + + +@trollius.coroutine +def stats(client_reader, client_writer): + + try: + log.info('Clients: %s', SERVER.active_count) + client_writer.write(( + "HTTP/1.0 200 OK\n" + "Cache: nocache\n\n" + ).encode()) + client_writer.write(('data: %s\n\n' % SERVER.active_count).encode()) + yield trollius.From(client_writer.drain()) + + except trollius.ConnectionResetError as err: + log.info(err) + pass + finally: + client_writer.close() + return + + +def main(): + global SERVER + + try: + loop = trollius.get_event_loop() + coro = trollius.start_server( + handle_client, + host=None, + port=pagure.APP.config['EVENTSOURCE_PORT'], + loop=loop) + SERVER = loop.run_until_complete(coro) + log.info('Serving server at {}'.format(SERVER.sockets[0].getsockname())) + if pagure.APP.config.get('EV_STATS_PORT'): + stats_coro = trollius.start_server( + stats, + host=None, + port=pagure.APP.config.get('EV_STATS_PORT'), + loop=loop) + stats_server = loop.run_until_complete(stats_coro) + log.info('Serving stats at {}'.format( + stats_server.sockets[0].getsockname())) + loop.run_forever() + except KeyboardInterrupt: + pass + except trollius.ConnectionResetError as err: + log.exception("ERROR: ConnectionResetError in main") + except Exception as err: + log.exception("ERROR: Exception in main") + finally: + # Close the server + SERVER.close() + if pagure.APP.config.get('EV_STATS_PORT'): + stats_server.close() + log.info("End Connection") + loop.run_until_complete(SERVER.wait_closed()) + loop.close() + log.info("End") + + +if __name__ == '__main__': + log = logging.getLogger("") + formatter = logging.Formatter( + "%(asctime)s %(levelname)s [%(module)s:%(lineno)d] %(message)s") + + # setup console logging + log.setLevel(logging.DEBUG) + ch = logging.StreamHandler() + ch.setLevel(logging.DEBUG) + + aslog = logging.getLogger("asyncio") + aslog.setLevel(logging.DEBUG) + + ch.setFormatter(formatter) + log.addHandler(ch) + main() diff --git a/ev-server/pagure_ev.service b/ev-server/pagure_ev.service new file mode 100644 index 0000000..d980441 --- /dev/null +++ b/ev-server/pagure_ev.service @@ -0,0 +1,14 @@ +[Unit] +Description=Pagure EventSource server (Allowing live refresh of the pages supporting it) +After=redis.target +Documentation=https://pagure.io/pagure + +[Service] +ExecStart=/usr/libexec/pagure-ev/pagure-stream-server.py +Type=simple +User=git +Group=git +Restart=on-failure + +[Install] +WantedBy=multi-user.target diff --git a/files/alembic.ini b/files/alembic.ini new file mode 100644 index 0000000..e0dc1d2 --- /dev/null +++ b/files/alembic.ini @@ -0,0 +1,60 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = /usr/share/pagure/alembic + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# max length of characters to apply to the +# "slug" field +#truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +#sqlalchemy.url = driver://user:pass@localhost/dbname +sqlalchemy.url = sqlite:////var/tmp/pagure_dev.sqlite + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/files/api_key_expire_mail.py b/files/api_key_expire_mail.py new file mode 100644 index 0000000..504a758 --- /dev/null +++ b/files/api_key_expire_mail.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +import os +import argparse +from datetime import datetime, timedelta + +from sqlalchemy.exc import SQLAlchemyError + + +if 'PAGURE_CONFIG' not in os.environ \ + and os.path.exists('/etc/pagure/pagure.cfg'): + print 'Using configuration file `/etc/pagure/pagure.cfg`' + os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' + +import pagure +from pagure import SESSION +from pagure.lib import model + + +def main(debug=False): + ''' The function that actually sends the email + in case the expiration date is near''' + + current_time = datetime.utcnow() + day_diff_for_mail = [5, 3, 1] + email_dates = [email_day.date() for email_day in \ + [current_time + timedelta(days=i) for i in day_diff_for_mail]] + + tokens = SESSION.query(model.Token).all() + + for token in tokens: + if token.expiration.date() in email_dates: + user = token.user + user_email = user.default_email + project = token.project + days_left = token.expiration.day - datetime.utcnow().day + subject = 'Pagure API key expiration date is near!' + text = '''Hi %s, \nYour Pagure API key for the project %s will expire + in %s day(s). Please get a new key for non-interrupted service. \n + Thanks, \nYour Pagure Admin. ''' % (user.fullname, project.name, days_left) + msg = pagure.lib.notify.send_email(text, subject, user_email) + if debug: + print 'Sent mail to %s' % user.fullname + if debug: + print 'Done' + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Script to send email before the api token expires') + parser.add_argument( + '--debug', dest='debug', action='store_true', default=False, + help='Print the debugging output') + args = parser.parse_args() + main(debug=args.debug) diff --git a/files/doc_pagure.wsgi b/files/doc_pagure.wsgi new file mode 100644 index 0000000..e2b4689 --- /dev/null +++ b/files/doc_pagure.wsgi @@ -0,0 +1,23 @@ +#-*- coding: utf-8 -*- + +# The three lines below are required to run on EL6 as EL6 has +# two possible version of python-sqlalchemy and python-jinja2 +# These lines make sure the application uses the correct version. +import __main__ +__main__.__requires__ = ['SQLAlchemy >= 0.8', 'jinja2 >= 2.4'] +import pkg_resources + +# Set the environment variable pointing to the configuration file +import os +os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' + + +# The following is only needed if you did not install pagure +# as a python module (for example if you run it from a git clone). +#import sys +#sys.path.insert(0, '/path/to/pagure/') + + +# The most import line to make the wsgi working +from pagure.docs_server import APP as application +#application.debug = True diff --git a/files/emoji_clean_json.py b/files/emoji_clean_json.py new file mode 100644 index 0000000..8ffa743 --- /dev/null +++ b/files/emoji_clean_json.py @@ -0,0 +1,32 @@ +#!/usr/bin/env python3 + +import json +import os +import sys + +data = None +with open('emoji_strategy.json') as stream: + data = json.load(stream) + +if not data: + print 'Could not load the data from the JSON file' + sys.exit(1) + +# Retrieve the items we keep in the JSON +tokeep = {} +for key in data: + if '-' in data[key]['unicode'] and data[key]['unicode'].startswith('1F'): + continue + tokeep[key] = data[key] + +# Check if we have the keys of all images we kept + +unicodes = [tokeep[key]['unicode'] for key in tokeep] +images = [item.replace('.png', '') for item in os.listdir('png')] + +print set(unicodes).symmetric_difference(set(images)) + + +with open('emoji_strategy2.json', 'w') as stream: + json.dump(tokeep, stream) + diff --git a/files/gitolite.rc b/files/gitolite.rc new file mode 100755 index 0000000..2481600 --- /dev/null +++ b/files/gitolite.rc @@ -0,0 +1,233 @@ +# paths and configuration variables for gitolite + +# please read comments before editing + +# this file is meant to be pulled into a perl program using "do" or "require". + +# You do NOT need to know perl to edit the paths; it should be fairly +# self-explanatory and easy to maintain perl syntax :-) + +# -------------------------------------- +# Do not uncomment these values unless you know what you're doing +# $GL_PACKAGE_CONF = ""; +# $GL_PACKAGE_HOOKS = ""; + +# -------------------------------------- + +# -------------------------------------- + +# this is where the repos go. If you provide a relative path (not starting +# with "/"), it's relative to your $HOME. You may want to put in something +# like "/bigdisk" or whatever if your $HOME is too small for the repos, for +# example + +$REPO_BASE="/srv/git/repositories/"; + +# the default umask for repositories is 0077; change this if you run stuff +# like gitweb and find it can't read the repos. Please note the syntax; the +# leading 0 is required + +$REPO_UMASK = 0002; +# $REPO_UMASK = 0027; # gets you 'rwxr-x---' +# $REPO_UMASK = 0022; # gets you 'rwxr-xr-x' + +# part of the setup of gitweb is a variable called $projects_list (please see +# gitweb documentation for more on this). Set this to the same value: + +$PROJECTS_LIST = $ENV{HOME} . "/projects.list"; + +# -------------------------------------- + +# I see no reason anyone may want to change the gitolite admin directory, but +# feel free to do so. However, please note that it *must* be an *absolute* +# path (i.e., starting with a "/" character) + +# gitolite admin directory, files, etc + +$GL_ADMINDIR="/etc/gitolite"; + +# -------------------------------------- + +# templates for location of the log files and format of their names + +# I prefer this template (note the %y and %m placeholders) +# it produces files like `~/.gitolite/logs/gitolite-2009-09.log` + +$GL_LOGT="/var/log/gitolite/gitolite-%y-%m.log"; + +# other choices are below, or you can make your own -- but PLEASE MAKE SURE +# the directory exists and is writable; gitolite won't do that for you (unless +# it is the default, which is "$GL_ADMINDIR/logs") + +# $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y-%m-%d.log"; +# $GL_LOGT="$GL_ADMINDIR/logs/gitolite-%y.log"; + +# -------------------------------------- + +# Please DO NOT change these three paths + +$GL_CONF="$GL_ADMINDIR/conf/gitolite.conf"; +$GL_KEYDIR="$GL_ADMINDIR/keydir"; +$GL_CONF_COMPILED="$GL_ADMINDIR/conf/gitolite.conf-compiled.pm"; + +# -------------------------------------- + +# if git on your server is on a standard path (that is +# ssh git@server git --version +# works), leave this setting as is. Otherwise, choose one of the +# alternatives, or write your own + +$GIT_PATH=""; +# $GIT_PATH="/opt/bin/"; + +# -------------------------------------- + +# ---------------------------------------------------------------------- +# BIG CONFIG SETTINGS + +# Please read doc/big-config.mkd for details + +$GL_BIG_CONFIG = 1; +$GL_NO_DAEMON_NO_GITWEB = 1; +$GL_NO_CREATE_REPOS = 1; +$GL_NO_SETUP_AUTHKEYS = 1; + + +# ---------------------------------------------------------------------- +# SECURITY SENSITIVE SETTINGS +# +# Settings below this point may have security implications. That +# usually means that I have not thought hard enough about all the +# possible ways to crack security if these settings are enabled. + +# Please see details on each setting for specifics, if any. +# ---------------------------------------------------------------------- + + + +# -------------------------------------- +# ALLOW REPO ADMIN TO SET GITCONFIG KEYS +# +# Gitolite allows you to set git repo options using the "config" keyword; see +# conf/example.conf for details and syntax. +# +# However, if you are in an installation where the repo admin does not (and +# should not) have shell access to the server, then allowing him to set +# arbitrary repo config options *may* be a security risk -- some config +# settings may allow executing arbitrary commands. +# +# You have 3 choices. By default $GL_GITCONFIG_KEYS is left empty, which +# completely disables this feature (meaning you cannot set git configs from +# the repo config). + +$GL_GITCONFIG_KEYS = ""; + +# The second choice is to give it a space separated list of settings you +# consider safe. (These are actually treated as a set of regular expression +# patterns, and any one of them must match). For example: +# $GL_GITCONFIG_KEYS = "core\.logAllRefUpdates core\..*compression"; +# allows repo admins to set one of those 3 config keys (yes, that second +# pattern matches two settings from "man git-config", if you look) +# +# The third choice (which you may have guessed already if you're familiar with +# regular expressions) is to allow anything and everything: +# $GL_GITCONFIG_KEYS = ".*"; + +# -------------------------------------- +# EXTERNAL COMMAND HELPER -- HTPASSWD + +# security note: runs an external command (htpasswd) with specific arguments, +# including a user-chosen "password". + +# if you want to enable the "htpasswd" command, give this the absolute path to +# whatever file apache (etc) expect to find the passwords in. + +$HTPASSWD_FILE = ""; + +# Look in doc/3 ("easier to link gitweb authorisation with gitolite" section) +# for more details on using this feature. + +# -------------------------------------- +# EXTERNAL COMMAND HELPER -- RSYNC + +# security note: runs an external command (rsync) with specific arguments, all +# presumably filled in correctly by the client-side rsync. + +# base path of all the files that are accessible via rsync. Must be an +# absolute path. Leave it undefined or set to the empty string to disable the +# rsync helper. + +$RSYNC_BASE = ""; + +# $RSYNC_BASE = "/home/git/up-down"; +# $RSYNC_BASE = "/tmp/up-down"; + +# -------------------------------------- +# EXTERNAL COMMAND HELPER -- SVNSERVE + +# security note: runs an external command (svnserve) with specific arguments, +# as specified below. %u is substituted with the username. + +# This setting allows launching svnserve when requested by the ssh client. +# This allows using the same SSH setup (hostname/username/public key) for both +# SVN and git access. Leave it undefined or set to the empty string to disable +# svnserve access. + +$SVNSERVE = ""; +# $SVNSERVE = "/usr/bin/svnserve -r /var/svn/ -t --tunnel-user=%u"; + +# -------------------------------------- +# ALLOW REPO CONFIG TO USE WILDCARDS + +# security note: this used to in a separate "wildrepos" branch. You can +# create repositories based on wild cards, give "ownership" to the specific +# user who created it, allow him/her to hand out R and RW permissions to other +# users to collaborate, etc. This is powerful stuff, and I've made it as +# secure as I can, but it hasn't had the kind of rigorous line-by-line +# analysis that the old "master" branch had. + +# This has now been rolled into master, with all the functionality gated by +# this variable. Set this to 1 if you want to enable the wildrepos features. +# Please see doc/4-wildcard-repositories.mkd for details. + +$GL_WILDREPOS = 0; + +# -------------------------------------- +# DEFAULT WILDCARD PERMISSIONS + +# If set, this value will be used as the default user-level permission rule of +# new wildcard repositories. The user can change this value with the setperms command +# as desired after repository creation; it is only a default. Note that @all can be +# used here but is special; no other groups can be used in user-level permissions. + +# $GL_WILDREPOS_DEFPERMS = 'R = @all'; + +# -------------------------------------- +# HOOK CHAINING + +# by default, the update hook in every repo chains to "update.secondary". +# Similarly, the post-update hook in the admin repo chains to +# "post-update.secondary". If you're fine with the defaults, there's no need +# to do anything here. However, if you want to use different names or paths, +# change these variables + +# $UPDATE_CHAINS_TO = "hooks/update.secondary"; +# $ADMIN_POST_UPDATE_CHAINS_TO = "hooks/post-update.secondary"; + +# -------------------------------------- +# ADMIN DEFINED COMMANDS + +# WARNING: Use this feature only if (a) you really really know what you're +# doing or (b) you really don't care too much about security. Please read +# doc/admin-defined-commands.mkd for details. + +# $GL_ADC_PATH = ""; + +# -------------------------------------- +# per perl rules, this should be the last line in such a file: +1; + +# Local variables: +# mode: perl +# End: +# vim: set syn=perl: diff --git a/files/gitolite3.rc b/files/gitolite3.rc new file mode 100644 index 0000000..1a20d42 --- /dev/null +++ b/files/gitolite3.rc @@ -0,0 +1,195 @@ +# configuration variables for gitolite + +# This file is in perl syntax. But you do NOT need to know perl to edit it -- +# just mind the commas, use single quotes unless you know what you're doing, +# and make sure the brackets and braces stay matched up! + +# (Tip: perl allows a comma after the last item in a list also!) + +# HELP for commands can be had by running the command with "-h". + +# HELP for all the other FEATURES can be found in the documentation (look for +# "list of non-core programs shipped with gitolite" in the master index) or +# directly in the corresponding source file. + +%RC = ( + + # ------------------------------------------------------------------ + + # default umask gives you perms of '0700'; see the rc file docs for + # how/why you might change this + UMASK => 0077, + + # look for "git-config" in the documentation + GIT_CONFIG_KEYS => '', + + # comment out if you don't need all the extra detail in the logfile + LOG_EXTRA => 1, + # syslog options + # 1. leave this section as is for normal gitolite logging + # 2. uncomment this line to log only to syslog: + # LOG_DEST => 'syslog', + # 3. uncomment this line to log to syslog and the normal gitolite log: + # LOG_DEST => 'syslog,normal', + + # roles. add more roles (like MANAGER, TESTER, ...) here. + # WARNING: if you make changes to this hash, you MUST run 'gitolite + # compile' afterward, and possibly also 'gitolite trigger POST_COMPILE' + ROLES => { + READERS => 1, + WRITERS => 1, + }, + + # enable caching (currently only Redis). PLEASE RTFM BEFORE USING!!! + # CACHE => 'Redis', + + # ------------------------------------------------------------------ + + # rc variables used by various features + + # the 'info' command prints this as additional info, if it is set + # SITE_INFO => 'Please see http://blahblah/gitolite for more help', + + # the CpuTime feature uses these + # display user, system, and elapsed times to user after each git operation + # DISPLAY_CPU_TIME => 1, + # display a warning if total CPU times (u, s, cu, cs) crosses this limit + # CPU_TIME_WARN_LIMIT => 0.1, + + # the Mirroring feature needs this + # HOSTNAME => "foo", + + # TTL for redis cache; PLEASE SEE DOCUMENTATION BEFORE UNCOMMENTING! + # CACHE_TTL => 600, + + # ------------------------------------------------------------------ + + # suggested locations for site-local gitolite code (see cust.html) + + # this one is managed directly on the server + # LOCAL_CODE => "$ENV{HOME}/local", + + # or you can use this, which lets you put everything in a subdirectory + # called "local" in your gitolite-admin repo. For a SECURITY WARNING + # on this, see http://gitolite.com/gitolite/non-core.html#pushcode + # LOCAL_CODE => "$rc{GL_ADMIN_BASE}/local", + + # ------------------------------------------------------------------ + + # List of commands and features to enable + + ENABLE => [ + + # COMMANDS + + # These are the commands enabled by default + 'help', + 'desc', + 'info', + 'perms', + 'writable', + + # Uncomment or add new commands here. + # 'create', + # 'fork', + # 'mirror', + # 'readme', + # 'sskm', + # 'D', + + # These FEATURES are enabled by default. + + # essential (unless you're using smart-http mode) + 'ssh-authkeys', + + # creates git-config enties from gitolite.conf file entries like 'config foo.bar = baz' + 'git-config', + + # creates git-daemon-export-ok files; if you don't use git-daemon, comment this out + 'daemon', + + # creates projects.list file; if you don't use gitweb, comment this out + #'gitweb', + + # These FEATURES are disabled by default; uncomment to enable. If you + # need to add new ones, ask on the mailing list :-) + + # user-visible behaviour + + # prevent wild repos auto-create on fetch/clone + # 'no-create-on-read', + # no auto-create at all (don't forget to enable the 'create' command!) + # 'no-auto-create', + + # access a repo by another (possibly legacy) name + # 'Alias', + + # give some users direct shell access. See documentation in + # sts.html for details on the following two choices. + # "Shell $ENV{HOME}/.gitolite.shell-users", + # 'Shell alice bob', + + # set default roles from lines like 'option default.roles-1 = ...', etc. + # 'set-default-roles', + + # show more detailed messages on deny + # 'expand-deny-messages', + + # show a message of the day + # 'Motd', + + # system admin stuff + + # enable mirroring (don't forget to set the HOSTNAME too!) + # 'Mirroring', + + # allow people to submit pub files with more than one key in them + # 'ssh-authkeys-split', + + # selective read control hack + # 'partial-copy', + + # manage local, gitolite-controlled, copies of read-only upstream repos + # 'upstream', + + # updates 'description' file instead of 'gitweb.description' config item + # 'cgit', + + # allow repo-specific hooks to be added + # 'repo-specific-hooks', + + # performance, logging, monitoring... + + # be nice + # 'renice 10', + + # log CPU times (user, system, cumulative user, cumulative system) + # 'CpuTime', + + # syntactic_sugar for gitolite.conf and included files + + # allow backslash-escaped continuation lines in gitolite.conf + # 'continuation-lines', + + # create implicit user groups from directory names in keydir/ + # 'keysubdirs-as-groups', + + # allow simple line-oriented macros + # 'macros', + + # Kindergarten mode + + # disallow various things that sensible people shouldn't be doing anyway + # 'Kindergarten', + ], + +); + +# ------------------------------------------------------------------------------ +# per perl rules, this should be the last line in such a file: +1; + +# Local variables: +# mode: perl +# End: +# vim: set syn=perl: diff --git a/files/load_from_disk.py b/files/load_from_disk.py new file mode 100644 index 0000000..4f8d124 --- /dev/null +++ b/files/load_from_disk.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +import argparse +import requests +import os + +from sqlalchemy.exc import SQLAlchemyError + +if 'PAGURE_CONFIG' not in os.environ \ + and os.path.exists('/etc/pagure/pagure.cfg'): + print 'Using configuration file `/etc/pagure/pagure.cfg`' + os.environ['PAGURE_CONFIG'] = '/etc/pagure/pagure.cfg' + +import pagure + + +def get_poc_of_pkgs(debug=False): + """ Retrieve a dictionary giving the point of contact of each package + in pkgdb. + """ + if debug: + print 'Querying pkgdb' + PKGDB_URL = 'https://admin.fedoraproject.org/pkgdb/api/' + req = requests.get(PKGDB_URL + 'bugzilla').text + if debug: + print 'Pkgdb data retrieved, getting POC' + pkgs = {} + for line in req.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + line = line.split('|') + if len(line) < 4: + continue + pkgs[line[1]] = line[3] + + return pkgs + + +def main(folder, debug=False): + """ + Logic: + - Query the list of maintainer/PoC from pkgdb + - Browse the directory + - For each git in the directory, create the project with the correct POC + """ + pocs = get_poc_of_pkgs(debug=debug) + + if debug: + print 'Adding the user to the DB' + for user in sorted(set(pocs.values())): + if debug: + print user + try: + pagure.lib.set_up_user( + session=pagure.SESSION, + username=user, + fullname=user, + default_email='%s@fedoraproject.org' % user, + keydir=pagure.APP.config.get('GITOLITE_KEYDIR', None), + ) + pagure.SESSION.commit() + except SQLAlchemyError as err: + pagure.SESSION.rollback() + print 'ERROR with user %s' % user + print err + + for project in sorted(os.listdir(folder)): + if debug: + print project + + if not project.endswith('.git'): + if debug: + print ' -skip: not a git repository' + continue + + if project.split('.git')[0] not in pocs: + if debug: + print ' -skip: no pocs' + continue + + try: + name = project.split('.git')[0] + pagure.lib.new_project( + session=pagure.SESSION, + user=pocs[name], + name=name, + blacklist=pagure.APP.config['BLACKLISTED_PROJECTS'], + gitfolder=pagure.APP.config['GIT_FOLDER'], + docfolder=pagure.APP.config['DOCS_FOLDER'], + ticketfolder=pagure.APP.config['TICKETS_FOLDER'], + requestfolder=pagure.APP.config['REQUESTS_FOLDER'], + ) + pagure.SESSION.commit() + except pagure.exceptions.PagureException as err: + print 'ERROR with project %s' % project + print err + except SQLAlchemyError as err: # pragma: no cover + pagure.SESSION.rollback() + print 'ERROR (DB) with project %s' % project + print err + + +if __name__ == '__main__': + parser = argparse.ArgumentParser( + description='Script creating projects on pagure based on the git ' + 'repos present in the specified folder and the pkgdb information.' + ) + parser.add_argument( + 'folder', + help='Folder containing all the git repos of the projects to create') + parser.add_argument( + '--debug', dest='debug', action='store_true', default=False, + help='Print the debugging output') + + args = parser.parse_args() + + main(args.folder, debug=args.debug) diff --git a/files/pagure.cfg.sample b/files/pagure.cfg.sample new file mode 100644 index 0000000..a160c98 --- /dev/null +++ b/files/pagure.cfg.sample @@ -0,0 +1,214 @@ +import os +from datetime import timedelta + +### Set the time after which the admin session expires +# There are two sessions on pagure, login that holds for 31 days and +# the session defined here after which an user has to re-login. +# This session is used when accessing all administrative parts of pagure +# (ie: changing a project's or a user's settings) +ADMIN_SESSION_LIFETIME = timedelta(minutes=20) + +### Secret key for the Flask application +SECRET_KEY='' + +### url to the database server: +#DB_URL=mysql://user:pass@host/db_name +#DB_URL=postgres://user:pass@host/db_name +DB_URL = 'sqlite:////var/tmp/pagure_dev.sqlite' + +### The FAS group in which the admin of pagure are +ADMIN_GROUP = ['sysadmin-main'] + +### Hard-coded list of global admins +PAGURE_ADMIN_USERS = [] + +### The email address to which the flask.log will send the errors (tracebacks) +EMAIL_ERROR = 'pingou@pingoured.fr' + +### SMTP settings +SMTP_SERVER = 'localhost' +SMTP_PORT = 25 +SMTP_SSL = False + +#Specify both for enabling SMTP with auth +SMTP_USERNAME = None +SMTP_PASSWORD = None + +### Information used to sent notifications +FROM_EMAIL = 'pagure@pagure.io' +DOMAIN_EMAIL_NOTIFICATIONS = 'pagure.io' +SALT_EMAIL = '' + +### The URL at which the project is available. +APP_URL = 'https://pagure.io/' +### The URL at which the documentation of projects will be available +## This should be in a different domain to avoid XSS issues since we want +## to allow raw html to be displayed (different domain, ie not a sub-domain). +DOC_APP_URL = 'https://docs.pagure.org' + +### The URL to use to clone git repositories. +GIT_URL_SSH = 'ssh://git@pagure.io/' +GIT_URL_GIT = 'git://pagure.io/' + +### Folder containing to the git repos +GIT_FOLDER = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'repos' +) + +### Folder containing the forks repos +FORK_FOLDER = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'forks' +) + +### Folder containing the docs repos +DOCS_FOLDER = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'docs' +) + +### Folder containing the tickets repos +TICKETS_FOLDER = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'tickets' +) + +### Folder containing the pull-requests repos +REQUESTS_FOLDER = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'requests' +) + +### Folder containing the clones for the remote pull-requests +REMOTE_GIT_FOLDER = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'remotes' +) + + +### Configuration file for gitolite +GITOLITE_CONFIG = os.path.join( + os.path.abspath(os.path.dirname(__file__)), + '..', + 'gitolite.conf' +) + + +### Home folder of the gitolite user +### Folder where to run gl-compile-conf from +GITOLITE_HOME = None + +### Version of gitolite used: 2 or 3? +GITOLITE_VERSION = 3 + +### Folder containing all the public ssh keys for gitolite +GITOLITE_KEYDIR = None + +### Path to the gitolite.rc file +GL_RC = None + +### Path to the /bin directory where the gitolite tools can be found +GL_BINDIR = None + + +# SSH Information + +### The ssh certificates of the git server to be provided to the user +### /!\ format is important +# SSH_KEYS = {'RSA': {'fingerprint': '', 'pubkey': ''}} + + + +# Optional configuration + +### Number of items displayed per page +# Used when listing items +ITEM_PER_PAGE = 50 + +### Maximum size of the uploaded content +# Used to limit the size of file attached to a ticket for example +MAX_CONTENT_LENGTH = 4 * 1024 * 1024 # 4 megabytes + +### Lenght for short commits ids or file hex +SHORT_LENGTH = 6 + +### List of blacklisted project names that can conflicts for pagure's URLs +### or other +BLACKLISTED_PROJECTS = [ + 'static', 'pv', 'releases', 'new', 'api', 'settings', + 'logout', 'login', 'users', 'groups', 'projects'] + +### IP addresses allowed to access the internal endpoints +### These endpoints are used by the milter and are security sensitive, thus +### the IP filter +IP_ALLOWED_INTERNAL = ['127.0.0.1', 'localhost', '::1'] + +### EventSource/Web-Hook/Redis configuration +# The eventsource integration is what allows pagure to refresh the content +# on your page when someone else comments on the ticket (and this without +# asking you to reload the page. +# By default it is off, ie: EVENTSOURCE_SOURCE is None, to turn it on, specify +# here what the URL of the eventsource server is, for example: +# https://ev.pagure.io or https://pagure.io:8080 or whatever you are using +# (Note: the urls sent to it start with a '/' so no need to add one yourself) +EVENTSOURCE_SOURCE = None +# Port where the event source server is running (maybe be the same port +# as the one specified in EVENTSOURCE_SOURCE or a different one if you +# have something running in front of the server such as apache or stunnel). +EVENTSOURCE_PORT = 8080 +# If this port is specified, the event source server will run another server +# at this port and will provide information about the number of active +# connections running on the first (main) event source server +#EV_STATS_PORT = 8888 +# Web-hook can be turned on or off allowing using them for notifications, or +# not. +WEBHOOK = False + +### Redis configuration +# A redis server is required for both the Event-Source server or the web-hook +# server. +REDIS_HOST = '0.0.0.0' +REDIS_PORT = 6379 +REDIS_DB = 0 + +# Authentication related configuration option + +### Switch the authentication method +# Specify which authentication method to use, defaults to `fas` can be or +# `local` +# Default: ``fas``. +PAGURE_AUTH = 'fas' + +# When this is set to True, the session cookie will only be returned to the +# server via ssl (https). If you connect to the server via plain http, the +# cookie will not be sent. This prevents sniffing of the cookie contents. +# This may be set to False when testing your application but should always +# be set to True in production. +# Default: ``True``. +SESSION_COOKIE_SECURE = False + +# The name of the cookie used to store the session id. +# Default: ``.pagure``. +SESSION_COOKIE_NAME = 'pagure' + +# Boolean specifying whether to check the user's IP address when retrieving +# its session. This make things more secure (thus is on by default) but +# under certain setup it might not work (for example is there are proxies +# in front of the application). +CHECK_SESSION_IP = True + +# Used by SESSION_COOKIE_PATH +APPLICATION_ROOT = '/' + +# Allow the backward compatiblity endpoints for the old URLs schema to +# see the commits of a repo. This is only interesting if you pagure instance +# was running since before version 1.3 and if you care about backward +# compatibility in your URLs. +OLD_VIEW_COMMIT_ENABLED = False diff --git a/files/pagure.conf b/files/pagure.conf new file mode 100644 index 0000000..5a84743 --- /dev/null +++ b/files/pagure.conf @@ -0,0 +1,114 @@ +#WSGISocketPrefix run/wsgi +##WSGIRestrictStdout On +#WSGIRestrictSignal Off +#WSGIPythonOptimize 1 +#WSGIPassAuthorization On +#WSGIDaemonProcess pagure user=git group=git maximum-requests=1000 display-name=pagure processes=4 threads=4 inactivity-timeout=300 +## It is important that the doc server runs in a different apache process +#WSGIDaemonProcess paguredocs user=git group=git maximum-requests=1000 display-name=pagure processes=4 threads=4 inactivity-timeout=300 + +# + #ServerName pagure.io + #Redirect permanent / https://pagure.io/ +# + + +# + #ServerName docs.pagure.org + #Redirect permanent / https://docs.pagure.org/ +# + + +# + #ServerName docs.pagure.org + + #WSGIScriptAlias / /usr/share/pagure/docs_pagure.wsgi + + #SSLEngine on + #SSLProtocol all -SSLv2 -SSLv3 + ## Use secure TLSv1.1 and TLSv1.2 ciphers + #Header always add Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" + + #SSLCertificateFile /etc/pki/tls/....crt + #SSLCertificateChainFile /etc/pki/tls/....intermediate.crt + #SSLCertificateKeyFile /etc/pki/tls/....key + + #Alias /static /usr/lib/python2.7/site-packages/pagure/static/ + + # + #WSGIProcessGroup paguredocs + # + ## Apache 2.4 + #Require all granted + # + # + ## Apache 2.2 + #Order deny,allow + #Allow from all + # + # +# + + +# + #ServerName pagure.io + + #WSGIScriptAlias / /usr/share/pagure/pagure.wsgi + + #SSLEngine on + #SSLProtocol all -SSLv2 -SSLv3 + ## Use secure TLSv1.1 and TLSv1.2 ciphers + #Header always add Strict-Transport-Security "max-age=15768000; includeSubDomains; preload" + + #SSLCertificateFile /etc/pki/tls/....crt + #SSLCertificateChainFile /etc/pki/tls/....intermediate.crt + #SSLCertificateKeyFile /etc/pki/tls/....key + + #Alias /static /usr/lib/python2.7/site-packages/pagure/static/ + #Alias /releases /var/www/releases + + ## Section used to support cloning git repo over http (https in this case) + #SetEnv GIT_PROJECT_ROOT /srv/git/repositories + + #AliasMatch ^/(.*/objects/[0-9a-f]{2}/[0-9a-f]{38})$ /path/to/git/repositories/$1 + #AliasMatch ^/(.*/objects/pack/pack-[0-9a-f]{40}.(pack|idx))$ /path/to/git/repositories/$1 + #ScriptAliasMatch \ + #"(?x)^/(.*/(HEAD | \ + #info/refs | \ + #objects/info/[^/]+ | \ + #git-(upload|receive)-pack))$" \ + #/usr/libexec/git-core/git-http-backend/$1 + + # + #WSGIProcessGroup pagure + # + ## Apache 2.4 + #Require all granted + # + # + ## Apache 2.2 + #Order deny,allow + #Allow from all + # + # + + ## Folder where are stored the tarball of the releases + # + #WSGIProcessGroup pagure + # + ## Apache 2.4 + #Require all granted + # + # + ## Apache 2.2 + #Order deny,allow + #Allow from all + # + # + + # + #Options +Indexes + # + +# + diff --git a/files/pagure.spec b/files/pagure.spec new file mode 100644 index 0000000..6ece2f8 --- /dev/null +++ b/files/pagure.spec @@ -0,0 +1,1083 @@ +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from +%distutils.sysconfig import get_python_lib; print (get_python_lib())")} + +Name: pagure +Version: 2.3.4 +Release: 1%{?dist} +Summary: A git-centered forge + +License: GPLv2+ +URL: https://pagure.io/pagure +Source0: https://pagure.io/releases/pagure/%{name}-%{version}.tar.gz + +BuildArch: noarch + +BuildRequires: python2-devel +BuildRequires: python-setuptools +BuildRequires: python-nose + +BuildRequires: py-bcrypt +BuildRequires: python-alembic +BuildRequires: python-arrow +BuildRequires: python-binaryornot +BuildRequires: python-bleach +BuildRequires: python-blinker +BuildRequires: python-chardet +BuildRequires: python-cryptography +BuildRequires: python-docutils +BuildRequires: python-flask +BuildRequires: python-flask-wtf +BuildRequires: python-flask-multistatic +BuildRequires: python-markdown +BuildRequires: python-psutil +BuildRequires: python-pygit2 >= 0.20.1 +BuildRequires: python-pygments +BuildRequires: python-fedora +BuildRequires: python-openid +BuildRequires: python-openid-cla +BuildRequires: python-openid-teams +BuildRequires: python-straight-plugin +BuildRequires: python-wtforms +BuildRequires: python-munch +BuildRequires: python-enum34 +BuildRequires: python-redis + +# EPEL6 +%if ( 0%{?rhel} && 0%{?rhel} == 6 ) +BuildRequires: python-sqlalchemy0.8 +Requires: python-sqlalchemy0.8 +%else +BuildRequires: python-sqlalchemy > 0.8 +Requires: python-sqlalchemy > 0.8 +BuildRequires: systemd +%endif + +Requires: py-bcrypt +Requires: python-alembic +Requires: python-arrow +Requires: python-binaryornot +Requires: python-bleach +Requires: python-blinker +Requires: python-chardet +Requires: python-cryptography +Requires: python-docutils +Requires: python-enum34 +Requires: python-flask +Requires: python-flask-wtf +Requires: python-flask-multistatic +Requires: python-markdown +Requires: python-psutil +Requires: python-pygit2 >= 0.20.1 +Requires: python-pygments +Requires: python-fedora +Requires: python-openid +Requires: python-openid-cla +Requires: python-openid-teams +Requires: python-straight-plugin +Requires: python-wtforms +Requires: python-munch +Requires: python-redis +Requires: mod_wsgi + +# No dependency of the app per se, but required to make it working. +Requires: gitolite3 + +%description +Pagure is a light-weight git-centered forge based on pygit2. + +Currently, Pagure offers a web-interface for git repositories, a ticket +system and possibilities to create new projects, fork existing ones and +create/merge pull-requests across or within projects. + + +%package milters +Summary: Milter to integrate pagure with emails +BuildArch: noarch +BuildRequires: systemd-devel +Requires: python-pymilter +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +# It would work with sendmail but we configure things (like the tempfile) +# to work with postfix +Requires: postfix +%description milters +Milters (Mail filters) allowing the integration of pagure and emails. +This is useful for example to allow commenting on a ticket by email. + + +%package ev +Summary: EventSource server for pagure +BuildArch: noarch + +BuildRequires: systemd-devel +Requires: python-redis +Requires: python-trollius +Requires: python-trollius-redis +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +%description ev +Pagure comes with an eventsource server allowing live update of the pages +supporting it. This package provides it. + + +%package webhook +Summary: Web-Hook server for pagure +BuildArch: noarch + +BuildRequires: systemd-devel +Requires: python-redis +Requires: python-trollius +Requires: python-trollius-redis +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +%description webhook +Pagure comes with an webhook server allowing http callbacks for any action +done on a project. This package provides it. + + +%prep +%setup -q + + +%build +%{__python2} setup.py build + + +%install +%{__python2} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT + +# Install apache configuration file +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/httpd/conf.d/ +install -m 644 files/pagure.conf $RPM_BUILD_ROOT/%{_sysconfdir}/httpd/conf.d/pagure.conf + +# Install configuration file +mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/pagure +install -m 644 files/pagure.cfg.sample $RPM_BUILD_ROOT/%{_sysconfdir}/pagure/pagure.cfg + +# Install WSGI file +mkdir -p $RPM_BUILD_ROOT/%{_datadir}/pagure +install -m 644 files/pagure.wsgi $RPM_BUILD_ROOT/%{_datadir}/pagure/pagure.wsgi +install -m 644 files/doc_pagure.wsgi $RPM_BUILD_ROOT/%{_datadir}/pagure/doc_pagure.wsgi + +# Install the createdb script +install -m 644 createdb.py $RPM_BUILD_ROOT/%{_datadir}/pagure/pagure_createdb.py + +# Install the api_key_expire_mail.py script +install -m 644 createdb.py $RPM_BUILD_ROOT/%{_datadir}/pagure/api_key_expire_mail.py + +# Install the alembic configuration file +install -m 644 files/alembic.ini $RPM_BUILD_ROOT/%{_sysconfdir}/pagure/alembic.ini + +# Install the alembic revisions +cp -r alembic $RPM_BUILD_ROOT/%{_datadir}/pagure + + +# Install the milter files +mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/run/pagure +mkdir -p $RPM_BUILD_ROOT/%{_tmpfilesdir} +mkdir -p $RPM_BUILD_ROOT/%{_unitdir} +install -m 0644 milters/milter_tempfile.conf \ + $RPM_BUILD_ROOT/%{_tmpfilesdir}/%{name}-milter.conf +install -m 644 milters/pagure_milter.service \ + $RPM_BUILD_ROOT/%{_unitdir}/pagure_milter.service +install -m 644 milters/comment_email_milter.py \ + $RPM_BUILD_ROOT/%{_datadir}/pagure/comment_email_milter.py + +# Install the eventsource +mkdir -p $RPM_BUILD_ROOT/%{_libexecdir}/pagure-ev +install -m 755 ev-server/pagure-stream-server.py \ + $RPM_BUILD_ROOT/%{_libexecdir}/pagure-ev/pagure-stream-server.py +install -m 644 ev-server/pagure_ev.service \ + $RPM_BUILD_ROOT/%{_unitdir}/pagure_ev.service + +# Install the web-hook +mkdir -p $RPM_BUILD_ROOT/%{_libexecdir}/pagure-webhook +install -m 755 webhook-server/pagure-webhook-server.py \ + $RPM_BUILD_ROOT/%{_libexecdir}/pagure-webhook/pagure-webhook-server.py +install -m 644 webhook-server/pagure_webhook.service \ + $RPM_BUILD_ROOT/%{_unitdir}/pagure_webhook.service + +%post milters +%systemd_post pagure_milter.service +%post ev +%systemd_post pagure_ev.service +%post webhook +%systemd_post pagure_webhook.service + +%preun milters +%systemd_preun pagure_milter.service +%preun ev +%systemd_preun pagure_ev.service +%preun webhook +%systemd_preun pagure_webhook.service + +%postun milters +%systemd_postun_with_restart pagure_milter.service +%postun ev +%systemd_postun_with_restart pagure_ev.service +%postun webhook +%systemd_postun_with_restart pagure_webhook.service + + +%files +%doc README.rst UPGRADING.rst +%license LICENSE +%config(noreplace) %{_sysconfdir}/httpd/conf.d/pagure.conf +%config(noreplace) %{_sysconfdir}/pagure/pagure.cfg +%config(noreplace) %{_sysconfdir}/pagure/alembic.ini +%dir %{_sysconfdir}/pagure/ +%dir %{_datadir}/pagure/ +%config(noreplace) %{_datadir}/pagure/*.wsgi +%{_datadir}/pagure/*.py* +%{_datadir}/pagure/alembic/ +%{python_sitelib}/pagure/ +%{python_sitelib}/pagure*.egg-info + + +%files milters +%license LICENSE +%attr(755,postfix,postfix) %dir %{_localstatedir}/run/pagure +%dir %{_datadir}/pagure/ +%{_tmpfilesdir}/%{name}-milter.conf +%{_unitdir}/pagure_milter.service +%{_datadir}/pagure/comment_email_milter.py* + + +%files ev +%license LICENSE +%{_libexecdir}/pagure-ev/ +%{_unitdir}/pagure_ev.service + + +%files webhook +%license LICENSE +%{_libexecdir}/pagure-webhook/ +%{_unitdir}/pagure_webhook.service + + +%changelog +* Wed Jul 27 2016 Pierre-Yves Chibon - 2.3.4-1 +- Update to 2.3.4 +- Security fix release blocking all html related mimetype when displaying the + raw files in issues and forces the browser to download them instead (Thanks to + Patrick Uiterwijk for finding this issue) - CVE: CVE-2016-1000037 + +* Fri Jul 15 2016 Pierre-Yves Chibon - 2.3.3-1 +- Update to 2.3.3 +- Fix redering the release page when the tag message contain only spaces (Vivek + Anand) +- Fix the search in @ (Eric Barbour) +- Displays link and git sub-modules in the tree with a dedicated icon + +* Tue Jul 12 2016 Pierre-Yves Chibon - 2.3.2-1 +- Update to 2.3.2 +- Do not mark as local only some of the internal API endpoints since they are + called via ajax and thus with the user's IP + +* Mon Jul 11 2016 Pierre-Yves Chibon - 2.3.1-1 +- Update to 2.3.1 +- Fix sending notifications to users watching a project +- Fix displaying if you are watching the project or not + +* Mon Jul 11 2016 Pierre-Yves Chibon - 2.3-1 +- Update to 2.3 +- Fix typos in pr_custom_page.rst (Lubomír Sedlář) +- Improve the unit-test suite (Vivek Anand) +- Remove the branch chooser from the repoheader and rework the fork button (Ryan + Lerch) +- Add support for non utf-8 file names (Ryan Lerch) +- Add a 'Duplicate' status for issues (Vivek Anand) +- Add title attribute for replying to comment and editing the comment in issues + and PRs (Vivek Anand) +- Include the user when reporting error by email +- Add an API endpoint to create projects +- Add an API endpoint to assign someone to a ticket +- Add small script to be ran as cron to send reminder of expiring tokens (Vivek + Anand) +- Do not show the PR button on branches for which a PR is already opened +- Add an API endpoint to fork projects +- Add the possibility to watch/unwatch a project (Gaurav Kumar) +- Add a 'Take' button on the issue page (Ryan Lerch and I) +- Add a dev-data script to input some test data in the DB for testing/dev + purposes (skrzepto) +- Fix links to ticket/pull-request in the preview of a new ticket +- Add the possibility to diff two or more commits (Oliver Gutierrez) +- Fix viewing a file having a non-ascii name +- Fix viewing the diff between two commits having a file with a non-ascii name +- On the commit detail page, specify on which branch(es) the commit is +- Add the possibility to have instance-wide admins will full access to every + projects (set in the configuration file) +- Drop the hash to the blob of the file when listing the files in the repo +- Add autocomple/suggestion on typing @ on a ticket or a pull-request + (Eric Barbour) +- Fix the edit link when adding a comment to a ticket via SSE +- Add notifications to issues as we have for pull-requests +- Record in the db the date at which a ticket was closed (Vivek Anand) +- Add the possibility for pagure to rely on external groups provided by the auth + service +- Add the possibility for pagure to use an SMTP server requiring auth + (Vyacheslav Anzhiganov) +- Add autocomple/suggestion on typing # for tickets and pull-requests (Eric + Barbour) +- With creating a README when project's description has non-ascii characters + (vanzhiganov) +- Add colored label for duplicate status of issues (Vivek Anand) +- Ship working wsgi files so that they can be used directly from the RPM +- Mark the wsgi files provided with the RPM as %%config(noreplace) +- Install the api_key_expire_mail.py script next to the createdb one + +* Wed Jun 01 2016 Pierre-Yves Chibon - 2.2.1-1 +- Update to 2.2.1 +- Fix showing the inital comment on PR having only one commit (Ryan Lerch) +- Fix diffs not showing for additions/deletions for files under 1000 lines (Ryan + Lerch) +- Split out the commits page to a template of its own (Ryan Lerch) +- Fix hightlighting the commits tab on commit view +- Fix the fact that the no readme box show on empty repo (Ryan Lerch) + +* Tue May 31 2016 Pierre-Yves Chibon - 2.2-1 +- Update to 2.2 +- Fix retrieving the log level from the configuration file (Nuno Maltez) +- Rework the labels used when sorting projects (Ankush Behl) +- Fix spelling error in sample config (Bruno) +- Hide the URL to the git repo for issues if these are disabled +- Do not notify about tickets being assigned when loaded from the issue git repo + (Clément Verna) +- Adjust get_revs_between so that if the push is in the main branch we still get + the list of changes (Clément Verna) +- Fix display of files moved on both old and new pygit2 (Ryan Lerch) +- Fix changes summary sidebar for older versions of pygit (Ryan Lerch) +- Fix the label on the button to add a new milestone to a project (Lubomír + Sedlář) +- Allow the roadmap feature to have multiple milestone without dates (Lubomír + Sedlář) +- Fix the link to switch the roadmap/list views (Lubomír Sedlář) +- Render the emoji when adding a comment to a ticket or PR via SSE (Clément + Verna) +- Always allow adming to edit/delete comments on issues +- Build Require systemd to get macros defined in the spec file (Bruno) +- Upon creating a ticket if the form already has data, show that data +- Add a readme placeholder for projects without a readme (Ryan Lerch) +- Enable markdown preview on create pull request (Ryan Lerch) +- Make bottom pagination links on project list respect the sorting filter (Ryan + Lerch) +- Add the ability to create a README when creating a project (Ryan Lerch) +- Try to prevent pushing commits without a parent when there should be one +- Fix the configuration keys to turn off ticket or user/group management for an + entire instance (Vivek Anand) +- Fix deleting project (propagate the deletion to the plugins tables) +- Do not render the diffs of large added and removed files (more than 1000 + lines) (Ryan Lerch) +- Adjust the UI on the template to add/remove a group or an user to a project in + the settings page (Ryan Lerch) +- Check if a tag exists on a project before allowing to edit it (skrzepto) + +* Fri May 13 2016 Pierre-Yves Chibon - 2.1.1-1 +- Update to 2.1.1 +- Do not render the comment as markdown when importing tickets via the ticket + git repo +- Revert get_revs_between changes made in + https://pagure.io/pagure/pull-request/941 (Clement Verna) + +* Fri May 13 2016 Pierre-Yves Chibon - 2.1-1 +- Update to 2.1 +- Fix the milter to get it working (hotfixed in prod) +- Fix the fedmsg hook so that it works fine (hotfixed in prod) +- Fix the path of one of the internal API endpoint +- Pass client_encoding utf8 when connecting to the DB (Richard Marko) +- Do not use client_encoding if using sqlite (Ryan Lerch) +- Allow project names up to 255 characters (Richard Marko) +- Add a spinner showing we're working on retrieve the PR status on the PR page + (farhaanbukhsh) +- Rework installing and removing git hooks (Clement Verna) +- Rework the summary of the changes on the PR page (Ryan Lerch) +- Improve the description of the priority system (Lubomír Sedlář) +- Fix commit url in the pagure hook (Mike McLean) +- Improve the regex when fixing/relating a commit to a ticket or a PR (Mike + McLean) +- Improve the description of the pagure hook (Mike McLean) +- Fix the priority system to support tickets without priority +- Fix the ordering of the priority in the drop-down list of priorities +- Ensure the drop-down list of priorities defaults to the current priority +- Adjust the runserver.py script to setup PAGURE_CONFIG before importing pagure +- Remove flashed message when creating a new project +- Add markdown support for making of PR# a link to the corresponding PR +- Include the priority in the JSON representation of a ticket +- Include the priorities in the JSON representation of a project +- Do not update the assignee if the person who commented isn't an admin +- When adding a comment fails, include the comment text in the form if there was + one +- Add support to remove a group from a project +- Add a roadmap feature with corresponding documentation +- Allow 'kbd' and 'var' html tags to render properly +- Fix deleting a project on disk as well as in the DB +- Allow setting the date_created field when importing ticket from git (Clement + Verna) +- Strip GPG signature from the release message on the release page (Jan Pokorný) +- Make comment on PR diffs fit the parent, and not overflow horiz (Ryan Lerch) + +* Sun Apr 24 2016 Pierre-Yves Chibon - 2.0.1-1 +- Update to 2.0.1 +- Fixes to the UPGRADING documentation +- Fix URLs to the git repos shown in the overview page for forks +- Fix the project titles in the html to not start with `forks/` + +* Fri Apr 22 2016 Pierre-Yves Chibon - 2.0-1 +- Update to 2.0 +- Rework the initial comment of a PR, making it less a comment and more + something that belong to the PR itself +- Fix showing or not the fork button when editing a comment on an issue or a PR + and fix the highlighted tab when editing comment of an issue (Oliver + Gutierrez) +- Fix the count of comments shown on the page listing all the PRs to include + only the comments and not the notifications (farhaanbukhsh) +- In the settings page explain that API keys are personal (Lubomír Sedlář) +- Rework the fedmsg message sent upon pushing commits, one message per push + instead of one message per commit +- Mark the page next/previous as disabled when they are (on browse pages) +- Avoid the logout/login loop when logging out +- Support rendering file with a `.markdown` extension +- Fix the layout of the password change branch +- Improve the documentation, add overview graphs, expand the usage section, + improve the overview description +- Fix checking if the user is an admin of a project or not (which was making the + user experience confusing as they sometime had the fork button and sometime + not) +- Fix the pagination on the browse pages when the results are sorted +- Disable the Commit and Files tabs if a repo is new +- Update the pagure logo to look better (Ryan Lerch) +- Allow anyone to fork any project (Ryan Lerch) +- Fix searching on the browse pages by preventing submission of the 'enter' key + (Ryan Lerch) +- Rework the issue page to be a single, large form allowing to update the + meta-data and comment in one action and fixing updating the page via SSE +- Turn off the project's documentation by default to empty `Docs` tab leading to + nothing +- Fill the initial comment with the body of the commit message if the PR only + has one commit (Ryan Lerch) +- Add a plugin/git hook allowing to disable non fast-forward pushes on a branch + basis +- Fix asynchronous inline comments in PR by fixing the URL to which the form is + submitted +- Add a plugin/git hook allowing to trigger build on readthedocs.org upon git + push, with the possibility to restrict the trigger to only certain branches +- Automatically scroll to the highlighted range when viewing a file with a + selection (Lubomír Sedlář) +- Indicate the project's creation date in the overview page (Anthony Lackey) +- Clear the `preview` field after adding a comment via SSE +- Adjust the unit-tests for the change in behavior in pygments 2.1.3 +- Fix listing all the request when the status is True and do not convert to text + request.closed_at if it is in fact None +- Improved documentation +- Attempt to fix the error `too many open files` on the EventSource Server +- Add a new param to runserver.py to set the host (Ryan Lerch) +- Fix the of the Docs tab and the Fork button with rounded corners (Pedro Lima) +- Expand the information in the notifications message when a PR is updated (Ryan + Lerch) +- Fix hidding the reply buttons when users are not authenticated (Paul W. Frields) +- Improve the description of the git hooks (Lubomír Sedlář) +- Allow reply to a notification of pagure and setting the reply email address as + Cc +- In the fedmsg git hook, publish the username of all the users who authored the + commits pushed +- Add an activity page/feed for each project using the information retrieved + from datagrepper (Ryan Lerch) +- Fix showing lightweight tags in the releases page (Ryan Lerch) +- Fix showing the list of branches when viewing a file +- Add priorities to issues, with the possibility to filter or sort them by it in + the page listing them. +- Add support for pseudo-namespace to pagure (ie: allow one '/' in project name + with a limited set of prefix allowed) +- Add a new plugin/hook to block push containing commits missing the + 'Signed-off-by' line +- Ensure we always use the default email address when sending notification to + avoid potentially sending twice a notification +- Add support for using the keyword Merge(s|d) to close a ticket or pull-request + via a commit message (Patrick Uiterwijk) +- Add an UPGRADING.rst documentation file explaining how to upgrade between + pagure releases + +* Tue Mar 01 2016 Pierre-Yves Chibon - 1.2-1 +- Update to 1.2 +- Add the possibility to create a comment when opening a pull-request (Clement + Verna) +- Fix creating PR from a fork directly from the page listing all the PR on the + main project (Ryan Lerch) +- Color the label showing the issues' status on the issue page and the page + listing them (Ryan Lerch) +- Add a small padding at the bottom of the blockquote (Ryan Lerch) +- In the list of closed PR, replace the column of the assignee with the date of + closing (Ryan Lerch) +- Drop font awesome since we no longer use it and compress the png of the + current logo (Ryan Lerch) +- Drop the svg of the old logo from the source (Ryan Lerch) +- Add descriptions to the git hooks in the settings page (farhaanbukhsh) +- Fix the pagure git hook + +* Wed Feb 24 2016 Pierre-Yves Chibon - 1.1.1-1 +- Update to 1.1.1 +- Fix showing some files where decoding to UTF-8 was failing +- Avoid adding a notification to a PR for nothing +- Show notifications correctly on the PR page when received via SSE + +* Tue Feb 23 2016 Pierre-Yves Chibon - 1.1-1 +- Update to 1.1 +- Sort the release by commit time rather than name (Clerment Verna) +- Add a link to the markdown syntax we support +- Add the possibility to display custom info when creating a new PR +- Improve the title of the issue page +- Make the ssh_info page more flexible so that we can add new info more easily +- Add the possibility to resend a confirmation email when adding a new email + address +- Encode the email in UTF-8 for domain name supporting it +- Add a button to easily change your avatar in your settings' page (Clement + Verna) +- Expand our markdown processor to support implicit linking to both PR and + issues +- Fix running the unit-tests on F23 +- Fix deleting in the UI branches containing a slash ('/') in their name +- Add the possibility to always have a merge commit when merging a PR +- Add the project's avatar to the list in front page when authenticated +- Make the dependency on flask-fas-openid (part of python-fedora) optional +- Prevent our customized markdown to create link on foo.com if it doesn't start + with {f,ht}tp(s) (Clement Verna) +- Bring back the delete ticket button (Ryan Lerch) +- Add the possibility to notify someone when it is mentioned in a comment via + @username +- Fix setting the default value of the web-hook setting and its display in the + settings page +- Add the possibility to have templates for the issues +- Add a button on the doc page to open it in a new tab +- Add the concept of notifications on PR allowing to indicate when a PR is + updated or rebased +- Fix allowing people with non-ascii username to merge PR with a merge commit +- Add the possibility to theme your pagure instance and customized its layout at + will +- Add the possibility to always see inline-comments even if the file was changed + since +- Improve the error message given to the user upon error 500 (Patrick Uiterwijk) +- Stop relying on pygit2 to determine if a file is a binary file or not and + instead use the python library binaryornot +- Store in the DB the identifier of the tree when an inline comment is made to a + PR, this way it will be simpler to figure out a way to add the context of this + comment either by email on in the UI +- Add styling to blockquotes so that we see what is the quote and what is the + answer when replying to someone +- Prevent users from adding again an email pending confirmation +- Fix the preview box for long comment (Ryan Lerch) +- Add the possibility to sort the projects when browsing them (Ryan Lerch) + +* Thu Feb 04 2016 Pierre-Yves Chibon - 1.0.2-1 +- Update to 1.0.2 +- Rework the PR page (Ryan Lerch) +- Add ssh_info to blacklist in default config (Ryan Lerch) +- Restyle the ssh_info page (Ryan Lerch) +- Fix hiding the preview pane when creating an issue (Ryan Lerch) +- Indicate the number of comments on the PR when listing them (Ryan Lerch) +- Fix showing the links to issues when previewing a comment +- Ensure some more that the page number isn't below 1 +- Do not show the edit and delete buttons to everyone when adding a comment via + SSE +- Update the requirements.txt for a missing dependency on Ubuntu (vanzhiganov) +- Improving sorting the release tags in the release page (Clement Verna) + +* Mon Feb 01 2016 Pierre-Yves Chibon - 1.0.1-1 +- Update to 1.0.1 +- Improve the fork list (Ryan Lerch) +- Make sure the images on comments do not exceed the size of the comment + box/area (Ryan Lerch) +- Improve the page listing all issues (Ryan Lerch) +- Include the project information when sending a fedmsg message about editing a + comment +- Allow tags in rst files so that the README shows fine +- Fix linking directly to a specific comment in a PR +- Fix adding comment in a PR via SSE +- Fix updating issue information via SSE +- Fix the reply buttons on the issue page +- Remove the choice for a status when creating a new ticket (Farhaandukhsh) +- Fix deleting a branch from the UI +- Make the cards have rounded corners (Sayan Chowdhury) +- Fix showing the description of form field (Vivek Anand) +- Fix checking if the passwords added are the same (for local accounts) + (Vivek Anand) +- Fix displaying emojis when previewing a comment on a ticket (Clement Verna) +- Add support for emojis when creating a new ticket (Clement Verna) + +* Wed Jan 27 2016 Pierre-Yves Chibon - 1.0-1 +- Update to 1.0 +- Entirely new UI thanks to the hard work on Ryan Lerch +- Add the possibility to edit comments on PR/Tickets (and the option to disable + this) (farhaanbukhsh) +- Add the number of open Tickets/PR on the project's menu +- Also allow PRs to be closed via a git commit message (Patrick Uiterwijk) +- Disable issues and PR on forks by default (Vivek Anand) +- Fix deleting the temporary folders we create +- Un-bundle flask_fas_openid (requires python-fedora 0.7.0 or higher +- Add support for an openid backend (ie same thing as FAS but w/o the FPCA + enforcing) +- Add support to view rst/markdown files as html directly inline (default) or as + text (Yves Martin) +- Change the encryption system when using pagure with local auth to not be + time-sensitive and be stronger in general (farhaanbukhsh) +- Change the size of the varchar from 256 to 255 for a better MySQL support +- Add support for pagure to work behind a reverse proxy +- Rename the cla_required decorator to a more appropriate login_required +- Show the in the front page and the page listing all the pull-requests the + branch for which a PR can be opened +- Rework the avatar to not rely on the ones associated with id.fedoraproject.org +- Add support to high-light a section of code in a PR and show the diff + automatically if there is such selection + +* Mon Dec 14 2015 Pierre-Yves Chibon - 0.1.36-1 +- Update to 0.1.36 +- Add the ssh info on the front page if the repo is empty +- Make the code handling exception be python3 compatible +- Make pagure compatible with F23 (ie: pygit2 0.23.0) +- Fix pagination when rendering the repo blocks (Gaurav Kumar) +- Make the SHOW_PROJECTS_INDEX list what should be showing in the index page +- Adjust pagure to work on recent version of psutils as well as the old one +- Added 'projects' to the blacklisted list of projects (Gaurav Kumar) +- Removed delete icons for non group members on the group info page (Gaurav + Kumar) +- Fixed forbidden error for PR title editing (Gaurav Kumar) + +* Mon Nov 30 2015 Pierre-Yves Chibon - 0.1.35-1 +- Update to 0.1.35 +- Fix the web-hook server by preventing it to raise any exception (rather log + the errors) + +* Mon Nov 30 2015 Pierre-Yves Chibon - 0.1.34-1 +- Update to 0.1.34 +- Fix the encoding of the files we're displaying on the UI +- Fix commenting on the last line of a diff +- Fix returning error message from the internal API (shows the PR as conflicting + then) +- Fix stacktrace encountered in some repo if the content of a folder is empty + (or is a git submodule) +- Split the web-hooks into their own server +- If you try to fork a forked project, redirect the user to the fork +- Show the repo from and repo to when opening a new PR +- Add the pagination links at the bottom of the repo list as well +- Add the groups to the pool of users to notify upon changes to a project +- Hide private repo from user who do not have commit access + +* Fri Nov 20 2015 Pierre-Yves Chibon - 0.1.33-1 +- Update to 0.1.33 +- Prevent project with a name starting with a non-alphanumerical character + (Farhaanbukhsh) +- Ensure we appropriately set the private flag when creating an issue +- Add an activity graph on the user profile using datagrepper +- Sometime the identified we get is a Tag, not a commit (fixes traceback + received by email) +- Order the PR from the most recent to the oldest +- Fix the patch view of a PR when we cannot find one of the commit (fixes + traceback received by email) +- Allow user that are not admin to create a remote pull-request +- Fix closing the EV server by calling the appropriate variable +- Fix generating the diff of remote pull-request + +* Fri Nov 13 2015 Pierre-Yves Chibon - 0.1.32-1 +- Update to 0.1.32 +- Fix the example configuration file +- Make pagure work on MySQL +- Hide sections on the front page only if the user is logged out +- Fix the release page where sometime tags are commits +- Escape the raw html in markdown +- Decode the bytes returned by pygit2 to try to guess if the content is a text + or not +- Fix the 'Clear' button on the pull-request page (farhaanbukhsh) +- Fix installing pagure in a venv +- Fix uploading images when editing the first comment of a ticket +- Let the author of the merge commit be the user doing the merge +- Suggest the title of the PR only if it has one and only one commit in +- Do not hide sections on the user page if we set some to be hidden on the front + page +- Forward the head to the commits page to fix the pull-request button +- Ensure we create the git-daemon-export-ok when forking a repo (fixes cloning + over https) +- Add instructions on how to get pagure working in a venv (Daniel Mach) +- Improve the way we retrieve and check pygit2's version (Daniel Mach) + +* Tue Oct 13 2015 Pierre-Yves Chibon - 0.1.31-1 +- Forward the bail_on_tree boolean when iterating so that we know how to behave + when we run into a git tree (where we expected a git blob) + -> fixes error received by email + +* Tue Oct 13 2015 Pierre-Yves Chibon - 0.1.30-1 +- Fix error received by email by checking the right variable if it is a git tree + or a git blob +- Unless we explicitly accept all images tag, always filter them (fixes + attaching images to a ticket) + +* Tue Oct 13 2015 Pierre-Yves Chibon - 0.1.29-1 +- Use monospace fonts for online editing as well as comment on tickets and + pull-requests +- Fix online editing of symlinked files (such as the README) +- Handle potential error when converting from rst to html + +* Mon Oct 12 2015 Pierre-Yves Chibon - 0.1.28-1 +- Update to 0.1.28 +- Fix the call to noJS() in the pull-request template to avoid crashing +- Improve the runserver script in the sources +- Fix the projects pagination on the index page +- Create the git-daemon-export-ok file upon creating a new project/git +- Use first line of commit message for PR title when only one commit (Maciej + Lasyk) +- Show the tag message near the tag in the release page +- Set the default_email when creating a local user account + +* Mon Oct 05 2015 Pierre-Yves Chibon - 0.1.27-1 +- Update to 0.1.27 +- Skip writing empty ssh keys on disc +- Regenerate authorized_keys file on ssh key change (Patrick Uiterwijk) + +* Mon Oct 05 2015 Pierre-Yves Chibon - 0.1.26-1 +- Update to 0.1.26 +- Let admins close PRs as well + +* Mon Oct 05 2015 Pierre-Yves Chibon - 0.1.25-1 +- Update to 0.1.25 +- Improve the documentation (especially the part about configuring pagure and + all the options the configuration file supports) +- Remove the two trailing empty lines when showing a file online +- Add a link on the issue list to be able to filter all the unassigned issues +- Rework the layout of the pull-request page +- Rework the commit list in the PR page to allow showing the entire commit + message +- Let any user create remote pull-request otherwise what's the point? +- Add the possibility to edit the title of a pull-request +- Add a page listing all the pull-requests of an user (opened by or against) +- Add support for multiple ssh-keys (Patrick Uiterwijk) +- Ensure the authorized_keys file is generated by gitolite (Patrick Uiterwijk) +- Fix the regex for @ +- Improve the display of renamed files in PR +- Add option to disable entirely the user/group management from the UI +- Add an updated_on field to Pull-Request +- Add an closed_at field to Pull-Request +- Allow the submitter of a PR to close it (w/o merging it) +- Disable editing a pull-request when that one is closed/merged +- Add option to hide by default a part of the index page (ie: all the repos, the + user's repos or the user's forks) +- Drop the csrf_token from the error emails sent to the admins + +* Tue Sep 08 2015 Pierre-Yves Chibon - 0.1.24-1 +- Update to 0.1.24 +- Fix changelog to add the -release +- Block the tag on titles +- Better fedmsg notifications (for example for new branches or rebase) +- Support uploading multiple files at once +- Add a load_from_disk utility script to the sources +- Fix indentation to the right on very long pull-request + +* Sun Aug 30 2015 Pierre-Yves Chibon - 0.1.23-1 +- Update to 0.1.23 +- Return a 404 error if we can't find the doc repo asked +- Fix for #106 Allow setting the default branch of the git repo and in the UI + (Ghost-script) +- Improve unit-tests suite +- Add a global boolean to disable entirely tickets on all projects of a pagure + instance (with no way to re-set them per project) +- Do display uploading a tarball if it is not entirely configured +- Ensure we do not offer to reply by email if the milter is not set up +- Ensure there is no new line character on the msg-id and improve logging in the + milter +- Add a configuration key to globally disable creating projects +- Add a configuration key to globally disable deleting projects +- Add the possibility to search projects/users +- Drop links to the individual commits in a remote pull-request +- Input that are cleaned via the noJS filter are safe to be displayed (avoid + double HTML escaping) +- When writing the authorized_key file, encode the data in UTF-8 +- Makes page title easier to find in multi-tab cases (dhrish20) +- Fix authorized_keys file creation (Patrick Uiterwijk) +- Honor also symlinked README's in repo overview (Jan Pakorný) +- Fix the patch generation for remote PR +- Fix showing the comment's preview on the pull-request page +- Fix bug in checking if a PR can be merged + +* Fri Aug 07 2015 Pierre-Yves Chibon - 0.1.22-1 +- Update to 0.1.22 +- Adjust the README to the current state of pagure +- Rework how we integrate our custom tags into markdown to avoid the infinite + loop we run into once in a while + +* Wed Aug 05 2015 Pierre-Yves Chibon - 0.1.21-1 +- Update to 0.1.21 +- Make SSH protocol explicit for SSH URLs (Till Maas) +- Adjust the documentation (layout and content) +- Rework the doc server to allow showing html files directly +- Fix installing the pagure hook correctly (tickets and requests) +- Give proper attribution to the pagure logo to Micah Deen in the documentation +- Increase pull request text field lengths to 80 (Till Maas) +- Fix who can open a remote PR and the check that the repo allows PR +- If there is no commit and no content, it means we didn't find the file: 404 + +* Wed Jul 29 2015 Pierre-Yves Chibon - 0.1.20-1 +- Update to 0.1.20 +- Include the tags in the JSON representation of a project +- Add the ability to open a pull-request from a git repo not hosted on pagure +- Fix pagination when browsing the list of commits +- Fix the fork button when viewing the Settings of a project +- Adjust the example apache configuration file +- Add a favicon with pagure's logo +- Fix asynchronous commentting on pull-requests +- Start working on some documentation on how to install pagure +- Do no flash messages when a comment is submitted via javascript (ie: async) +- Do not blink the tittle of the page if the page is already on focus +- Retrieve ssh key from FAS and set it up in pagure if none is currently set-up +- Fix anchors for comments on the pull-request pages +- Fix checking the merge status of a PR when user is not logged in + +* Mon Jul 20 2015 Pierre-Yves Chibon - 0.1.19-1 +- Update to 0.1.19 +- Prettify the JSON stored in the git for tickets/requests... (Simo Sorce) +- Use the project name as subject tag in the notifications sent (Simo Sorce) +- Add an X-pagure header with either the pagure instance or the project name +- Reset the merge status of all the open PR when one is merged +- Add a second server listing the number of connections opened on the first + eventsource server +- Log the info instead of printing them in the eventsource server +- Split the documentation to a different wsgi application to avoid any risk of + cross-site forgery +- Fix the JS logic when adding a tag or a dependency to avoid having duplicates + in the input field +- Allow deleting a git branch of a project via the UI +- Include the font-awesome in the source rather than relying on an external cdn +- Do not try to connect to the eventsource server if we're not viewing a + pull-request +- Fix showing the first comment made on a PR via the eventsource server +- Fix showing the git URLs in the doc server +- Much better API documentation (Lei Yang) +- Handle showing closed PR that were not merged +- Fix refreshing the UI of private tickets via the eventsource (making calls to + the API to get the info while only getting what changed via the SSE) +- Fix the anchor links in the API documentation +- Blink the tab upon changes in the page +- Ensure we close both SSE server when stopping pagure_ev +- Let the HTML form trigger if we did not connect to the EV server successfully +- The admins of a repo are anyone with commit access to the repo, directly or + via a group +- Order the project by names in the front page (instead of creation date) +- Add the ability to tag a project +- Fix the fedmsg_hook when there are only deletions or only additions +- Add a new API endpoint allowing to search projects (by name, author, tag ...) +- Make pagure compatible with pygit 0.22.0 +- Adjust unit-tests for all these changes + +* Mon Jun 22 2015 Pierre-Yves Chibon - 0.1.18-1 +- Update to 0.1.18 +- Fix the eventsource server for CORS +- Fix showing/checking the merge status of a PR + +* Mon Jun 22 2015 Pierre-Yves Chibon - 0.1.17-1 +- Update to 0.1.17 +- Fix for missing docs of API issue add comment (Kunaal Jain) +- Fix the systemd init file +- Be more careful about the URL specified, it may be of the wrong format in the + eventsource server +- Allow configuring the port where the event source server runs in the + configuration +- Fix bug in filter_img_src introduced with its moved to the backend library + +* Thu Jun 18 2015 Pierre-Yves Chibon - 0.1.16-1 +- Update to 0.1.16 +- Clone all the remote branches when cloning a project +- Allow online editing to a new branch or any of the existing ones +- Allow the
html tags in markdown +- Add eventsource support in the ticket and pull-request pages + +* Tue Jun 16 2015 Pierre-Yves Chibon - 0.1.15-1 +- Update 0.1.15 +- Use a monospace font for the commit hash +- Remove duplicated "commit" id in the HTML (causing a graphical bug in the + commit page) +- Secure the input using the no_js filter instead of relying on a restrictive + regex for PR and issue titles +- Support ',' in the tags field since it's required to specify multiple tags + +* Fri Jun 12 2015 Pierre-Yves Chibon - 0.1.14-1 +- Update to 0.1.14 +- Remove all new lines characters from the ssh key uploaded +- Adjust the URL in the footer to point to https://pagure.io/pagure +- Fix displaying the time of a comment +- Forbid the use of spaces in group name +- Do not get the list of not-merged commits if there is only 1 branch in the + repo +- Display the error message if pagure.lib.add_group raises an exception +- Add a new setting enforcing that all commits in a PR are signed-off by their + author +- Enforce that all commits are signed-off by the author if the repo is + configured for this +- Also check for the signed-off status before merging a pull-request +- Adjust online-editing to allow specifying which email address to use in the + commit +- Add an avatar_email field to projects +- Change the PullRequest's status from a Boolean to a Text restricted at the DB + level (Allows to distinguish Open/Merged/Closed) +- Show in the pull-request view who merged the pull-request +- Specify who closed the pull-request in the API output +- Catch GitError when merging and checking merge status of a PR +- Hide the form to create pull-requests if the user is not an admin of the repo +- Replace the Pull-Request button by a Compare button if the user it not a repo + admin +- Set the title of the tab as URL hash to allow directly linking to it +- Adjust the API to be able to distinguish API authentication and UI + authentication +- Fix API documentation to create new issues +- Drop the status from the requirements to open a new issue via the API +- Expand the list of blacklisted project names +- Have the code tags behave like pre tags (html tags) +- Allow project to specify an URL and display it on their page +- Strip the ssh keys when writing them to the authorized_keys file +- Disable javascript in all the markdown fields +- Validate early the input submitted in the forms (using more or less strict + regex) +- If the session timed-out, redirect to the setting page after authentication + and inform the user that the action was canceled +- Catch PagureException when adjusting the project's settings +- Redirect the /api endpoint to the api documentation place +- Fix how is retrieved the list of emails to send the notification to +- Sanitize the html using bleach to avoid potential XSS exploit +- Do not give READ access to everyone on the tickets and pull-requests repos to + avoid leaking private tickets +- Adjust the unit-tests for all these changes + +* Fri Jun 05 2015 Pierre-Yves Chibon - 0.1.13-1 +- Update to 0.1.13 +- Do not show the edit button if the user cannot edit the file +- Fix who is allowed to drop comments +- Fix showing the drop comment button on issue comments +- Fix creating the pull-request for fast people like @lmacken +- Display the target of the PR as well as the origin in the PR page +- Limit the size of the lists on the front page + +* Fri Jun 05 2015 Pierre-Yves Chibon - 0.1.12-1 +- Update to 0.1.12 +- Fix the URL where the sources upload are done +- Upload the new sources under the project's name (be it project or + user/project) + +* Fri Jun 05 2015 Pierre-Yves Chibon - 0.1.11-1 +- Update to 0.1.11 +- Another fix for the fedmsg_hook git hook +- Adjust how we display the README page to avoid XSS there as well +- Add the possibility to disable plugins via the configuration file +- Present the git tags in the UI +- As soon as the API user present a token, validate it or not, even if the + endpoint would work without token +- Integrate alembic for DB scheme migration +- Cache the PR's merge status into the DB +- Only people with access to the project can add/remove API token +- Make the unit-tests run on bare repos as in prod +- First stab at online editing +- Simplify the API output to drop the project's settings where it doesn't + make sense +- First stag at allowing upstream to upload their release to pagure +- Fix merging a PR into another branch than master +- Reduce code duplication when checking if a PR can be merged or merging it +- Code style clean-up + +* Tue Jun 02 2015 Pierre-Yves Chibon - 0.1.10-1 +- Update to 0.1.10 +- Add support for URL ending with a trailing slash where it makes sense (so + we support both with and without trailing slash) +- Fix XSS issue by disabling +{% endblock %} diff --git a/pagure/templates/add_group.html b/pagure/templates/add_group.html new file mode 100644 index 0000000..11fe7c1 --- /dev/null +++ b/pagure/templates/add_group.html @@ -0,0 +1,30 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% set tag = "groups" %} +{% block title %}Create group{% endblock %} + + +{% block content %} + +

Create group

+ +
+
+ + + {{ render_field_in_row(form.group_name) }} + {%- if admin %} + {{ render_field_in_row(form.group_type) }} + {%- endif %} +
+

+ + + {{ form.csrf_token }} +

+
+
+ +{% endblock %} diff --git a/pagure/templates/add_group_project.html b/pagure/templates/add_group_project.html new file mode 100644 index 0000000..cec869a --- /dev/null +++ b/pagure/templates/add_group_project.html @@ -0,0 +1,70 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% set tag = "groups" %} + +{% block header %} + +{% endblock %} + +{% block title %}Add group - {{ repo.name }}{% endblock %} + + +{% block repo %} + +
+
+
+ Add group to the {{repo.name}} project +
+
+
+ +
+ + +
+ +

+ + + {{ form.csrf_token }} +

+
+
+
+
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + + +{% endblock %} diff --git a/pagure/templates/add_token.html b/pagure/templates/add_token.html new file mode 100644 index 0000000..d81f36a --- /dev/null +++ b/pagure/templates/add_token.html @@ -0,0 +1,40 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% set tag = "home" %} +{% block title %}Create token{% endblock %} + + +{% block repo %} +
+
+
+
+ Create a new token +
+
+
+ {% for acl in acls %} +
+ +
+ {% endfor %} + +
+ + + {{ form.csrf_token }} +
+
+
+
+
+
+{% endblock %} diff --git a/pagure/templates/add_user.html b/pagure/templates/add_user.html new file mode 100644 index 0000000..da2bb4e --- /dev/null +++ b/pagure/templates/add_user.html @@ -0,0 +1,68 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% set tag = "home" %} + +{% block header %} + +{% endblock %} + +{% block title %}Add user - {{ repo.name }}{% endblock %} + +{% block repo %} +
+
+
+ Add user to the {{repo.name}} project +
+
+
+ +
+ + +
+ +

+ + + {{ form.csrf_token }} +

+
+
+
+
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + + +{% endblock %} diff --git a/pagure/templates/admin_index.html b/pagure/templates/admin_index.html new file mode 100644 index 0000000..aecb740 --- /dev/null +++ b/pagure/templates/admin_index.html @@ -0,0 +1,50 @@ +{% extends "master.html" %} + +{% block title %}Home{% endblock %} +{% set tag = "admin" %} + + +{% block content %} + +

Admin section

+ +
    +
  • +
    + + {{ form.csrf_token }} +
    +
  • +
  • +
    + + {{ form.csrf_token }} +
    +
  • +
  • +
    + + {{ form.csrf_token }} +
    +

    + This action can be used in case you need to change the hook-token for + *all* the projects hosted on this pagure instance. This can be due to + security reason. Beware that using it will break every web-hook + consumer of every project hosted. +

    +
  • +
  • + + Groups/Users management + +
  • +
+ +{% endblock %} diff --git a/pagure/templates/api.html b/pagure/templates/api.html new file mode 100644 index 0000000..b035f06 --- /dev/null +++ b/pagure/templates/api.html @@ -0,0 +1,138 @@ +{% extends "master.html" %} + +{% block title %} API | pagure {% endblock %} + +{% set tag = "index" %} + +{% block content %} +
+
+
+

+ +   Pagure API Reference +

+
+ This documentation describes the Pagure API v{{ version[0] }} + revision {{ version[1] }}. +
+
+
+
+
+
+ + {{ api_doc |replace('h1', 'h2') }} + +

List of the API endpoints:

+ +

+ Projects + + + +

+
+ {% for html in projects %} + {{ html | InsertDiv | safe }} + {% endfor %} +
+ + {% if issues %} +

+ Issues + + + +

+
+ {% for html in issues %} + {{ html | InsertDiv | safe }} + {% endfor %} +
+ {% endif %} + +

+ Pull-requests + + + +

+
+ {% for html in requests %} + {{ html | InsertDiv | safe }} + {% endfor %} +
+ +

+ Users + + + +

+
+ {% for html in users %} + {{ html | InsertDiv | safe }} + {% endfor %} +
+ +

+ Extras + + + +

+ +
+ {% for html in extras %} + {{ html | InsertDiv |safe }} + {% endfor %} +
+ +
+
+
+
+{% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} diff --git a/pagure/templates/comment_update.html b/pagure/templates/comment_update.html new file mode 100644 index 0000000..f164364 --- /dev/null +++ b/pagure/templates/comment_update.html @@ -0,0 +1,35 @@ +{% if not is_js %} +{% extends "repo_master.html" %} +{% endif %} + +{% block repo %} +
+ +
+ + +
+ +
+ +
+ +
+ + {{ form.csrf_token }} +
+ + + +
+ + + +
+ +{% endblock %} diff --git a/pagure/templates/commit.html b/pagure/templates/commit.html new file mode 100644 index 0000000..63224de --- /dev/null +++ b/pagure/templates/commit.html @@ -0,0 +1,187 @@ +{% extends "repo_master.html" %} + +{% block title %}Commit - {{ repo.name }} - {{ commitid }}{% endblock %} +{% set tag = "commit"%} + +{% block repo %} + +{% set splitted_message = commit.message.split('\n') %} +
+

+ Commit {{ commitid|short }} + {{ splitted_message[0] }} +
+ raw + patch + tree + {% if commit.parents|length == 1 %} + parent + {% elif commit.parents|length > 1 %} +
+ + +
+ {% endif%} +
+

+
+ {% if commit.author| author2user == commit.committer| author2user %} + {{diff|count}} file{{'s' if diff|count > 1}} Authored and Committed by {{ commit.author | author2user |safe }} + {{commit.commit_time | humanize}} + {% else %} + {{diff|count}} file{{'s' if diff|count > 1}} Authored by {{ commit.author | author2user |safe }} + {{commit.commit_time | humanize}}, + Committed by {{ commit.committer | author2user |safe }} + {{commit.commit_time | humanize}}, + {% endif%} +
+
+ + + +
+ {% if splitted_message|length > 1 %} +
+        {% for message in splitted_message %}
+{{ message }}
+        {% endfor %}
+    
+ {% endif %} +
+ +{% set filecount = 0 %} +{% for patch in diff %} + {% set filecount = filecount + 1 %} +
+
+ {% if patch | hasattr('new_file_path') %} + + {{ patch.new_file_path | unicode }} + + {% elif patch | hasattr('delta') %} + + {{ patch.delta.new_file.path | unicode }} + + {% endif %} + {% if not patch.is_binary %} + {% if patch | hasattr('additions') %} + {% if (patch.additions + patch.deletions) %} + + {% if patch.additions > 0 %}+{{ patch.additions }} {% endif %} + {% if patch.deletions > 0 %}-{{ patch.deletions }}{% endif %} + + {% endif %} + {% else %} + {% if (patch.line_stats[1] + patch.line_stats[2]) %} + + {% if patch.line_stats[1] > 0 %}+{{ patch.line_stats[1] }} {% endif %} + {% if patch.line_stats[2] > 0 %}-{{ patch.line_stats[2] }}{% endif %} + + {% endif %} + {% endif %} + {% endif %} +
+ + {% if patch.is_binary %} +

Binary diffs cannot be rendered.

+ {% else %} + {% autoescape false %} + {{ patch|patch_to_diff|html_diff}} + {% endautoescape %} + {% endif %} +
+ +{% endfor %} + +{% endblock %} + + + +{% block jscripts %} + {{ super() }} + +{% endblock %} diff --git a/pagure/templates/commits.html b/pagure/templates/commits.html new file mode 100644 index 0000000..3bc23b7 --- /dev/null +++ b/pagure/templates/commits.html @@ -0,0 +1,178 @@ +{% extends "repo_master.html" %} + +{% block title %}{{ select.capitalize() }} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block repo %} +
+ {% if repo_obj and repo_obj.is_empty %} +
+ +
+ {% else %} +
+

+ Commits {{number_of_commits}} +

+
+ +
+
+
+ {% if repo.is_fork %} + + {{ repo.user.user }}/{{ repo.name }} + {% else %} + {{ repo.name }} + {% endif %} + +
+ {% if branchname %} +
+ + +
+ {% endif %} +
+
+
+ + {% if diff_commits|count > 0 %} + + {% endif %} + + + + {% if total_page %} + + {% endif %} + {% endif %} + +{% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} diff --git a/pagure/templates/doc_ssh_keys.html b/pagure/templates/doc_ssh_keys.html new file mode 100644 index 0000000..4f42b41 --- /dev/null +++ b/pagure/templates/doc_ssh_keys.html @@ -0,0 +1,40 @@ +{% extends "master.html" %} + +{% block title %}Doc: SSH hostkeys/Fingerprints{% endblock %} +{% set tag = "home" %} + + +{% block content %} +
+

SSH Hostkeys/Fingerprints

+ +{% if config.get('SSH_KEYS') %} + +

+ SSH host keys and fingerprints can be use to ensure you are connecting to +pagure's server and not someone else's. +

+ +

+Here below are the SSH Hostkey and the Fingerprint of this current pagure +instance: +

+ +{% for cert in config.get('SSH_KEYS') %} + {% for type in config['SSH_KEYS'][cert] %} +
+
+ {{ cert }} {{ type | title }} +
+
{{ config['SSH_KEYS'][cert][type] }}
+
+ {% endfor %} +{% endfor %} + +{% else %} +
No SSH keys have been specified in the configuration file. +You should ask an admin to fill this information.
+{% endif %} +
+ +{% endblock %} diff --git a/pagure/templates/docs.html b/pagure/templates/docs.html new file mode 100644 index 0000000..73492fa --- /dev/null +++ b/pagure/templates/docs.html @@ -0,0 +1,20 @@ +{% extends "repo_master.html" %} + +{% block title %}Docs - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block repo %} + + + +{% endblock %} diff --git a/pagure/templates/edit_file.html b/pagure/templates/edit_file.html new file mode 100644 index 0000000..bd8458d --- /dev/null +++ b/pagure/templates/edit_file.html @@ -0,0 +1,122 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field %} + +{% block title %}Edit - {{ repo.name }}{% endblock %} +{% set tag = "home" %} +{% block header %} + + +{% endblock%} + +{% block repo %} + +
+
+ +
+
+
+ {{ form.csrf_token }} + + + +
+
+ +
+ + {{ user.default_email | avatar(16) | safe }} + {{ user.fullname }} +
+
+
+ +
+ + Existing branch + New branch +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + + +{% endblock %} diff --git a/pagure/templates/edit_tag.html b/pagure/templates/edit_tag.html new file mode 100644 index 0000000..f406558 --- /dev/null +++ b/pagure/templates/edit_tag.html @@ -0,0 +1,28 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% block title %}Edit tag: {{ tag }} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block repo %} + +

Edit tag: {{ edit_tag }}

+ +
+
+ +

Enter in the field below the new name for the tag: "{{ edit_tag }}"

+ + {{ render_field_in_row(form.tag) }} +
+

+ + + {{ form.csrf_token }} +

+
+
+ +{% endblock %} diff --git a/pagure/templates/fatal_error.html b/pagure/templates/fatal_error.html new file mode 100644 index 0000000..9cec65d --- /dev/null +++ b/pagure/templates/fatal_error.html @@ -0,0 +1,16 @@ +{% extends "master.html" %} + +{% block title %}A fatal error occured :'({% endblock %} +{% set tag = "error" %} + + +{% block content %} +
+
+
+

Fatal Error (500)

+

A fatal error has occurred, we're sorry :(

+
+
+
+{% endblock %} diff --git a/pagure/templates/file.html b/pagure/templates/file.html new file mode 100644 index 0000000..ef559f3 --- /dev/null +++ b/pagure/templates/file.html @@ -0,0 +1,222 @@ +{% extends "repo_master.html" %} + +{% block title %}Tree - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block repo %} +
+
+

+ Files +

+
+ +
+
+
+ {% if repo.is_fork %} + + {{ repo.user.user }}/{{ repo.name }} + {% else %} + {{ repo.name }} + {% endif %} + +
+ {% if branchname %} +
+ + +
+ {% endif %} +
+
+
+
+
+ +
+ +{% if content %} + {% if output_type in ('file','binary','image','markup') %} +
+ {% if content %} + {% if output_type in ('file','binary','image','markup') %} +
+ {% if output_type in ('file','markup') and repo_admin %} + Edit + {% endif %} + {% if output_type == 'markup' %} + Text + {% else %} + Blob + {% endif %} + Raw +
+ {% endif %} + {% endif %} + + {% if output_type=='file' %} + {% autoescape false %} + {{ content | format_loc }} + {% endautoescape %} + {% elif output_type == 'markup' %} + {% autoescape false %} + {{ content | noJS }} + {% endautoescape %} + {% elif output_type == 'image' %} + + {% elif output_type == 'binary' %} +

+ Binary files cannot be rendered.
+ Please + view the raw version + +

+ {% endif %} +
+ {% else %} +
+ + + {% for entry in content %} + + + + {% endfor %} + +
+ {% if entry.filemode == 16384 %} + + {% elif entry.filemode == 40960 %} + + {% elif entry.filemode == 57344 %} + + {% else %} + + {% endif %} + + {% if entry.filemode == 16384 %}{% endif -%} + + {{ entry.name | unicode }} + {% if entry.filemode == 16384 %}{% endif%} +
+
+ {% endif %} +{% else %} +No content found in this repository +{% endif %} +
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} diff --git a/pagure/templates/forks.html b/pagure/templates/forks.html new file mode 100644 index 0000000..5b5a8f4 --- /dev/null +++ b/pagure/templates/forks.html @@ -0,0 +1,43 @@ +{% extends "repo_master.html" %} + +{% block title %}Forks - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block repo %} + +

Forks {{repo.forks | count}}

+ +
+ {% if repo.forks %} +
+ {% for fork in repo.forks %} + + + {% endfor %} +
+ {% else %} +

+ This project has not been forked. +

+ {% endif %} +
+ +{% endblock %} diff --git a/pagure/templates/group_info.html b/pagure/templates/group_info.html new file mode 100644 index 0000000..5632e87 --- /dev/null +++ b/pagure/templates/group_info.html @@ -0,0 +1,185 @@ +{% extends "master.html" %} +{% from "_render_repo.html" import render_repos_as_card %} + +{% block title %}Group {{ group.name }}{% endblock %} +{% set tag = "groups" %} +{% from "_browseheader.html" import browse_header %} + +{% block header %} + +{% endblock %} + +{% block content %} +
+
+ {{browse_header(select=tag)}} +
+
+
+
+
+

+  {{ group.group_name }} + {% if authenticated and (member or admin) + and config.get('ENABLE_GROUP_MNGT') %} +
+ {{ form.csrf_token }} + +
+ {% endif %} +

+
+ created {{ group.created |humanize }} + by {{ group.creator.fullname }} ({{ group.creator.user }}) +
+
+
+
+
+
+ {{ render_repos_as_card( + group.projects, group.projects|count, 'Projects') }} +
+
+
+
+ Group Members {{group.users|count}} + {% if authenticated and (member or admin) and config.get('ENABLE_GROUP_MNGT') %} +
+ +
+ {% endif %} +
+
+ + {% if authenticated and (member or admin) and config.get('ENABLE_GROUP_MNGT') %} + + {{ form.csrf_token }} + +
+ {% endif %} + + {% for user in group.users %} +
+ + {{ user.default_email | avatar(28) | safe }} + {{ user.user }} + + {% if authenticated and user != group.creator and (member or admin) + and config.get('ENABLE_GROUP_MNGT') %} +
+ {{ form.csrf_token }} + +
+ {% endif %} +
+ {% endfor %} +
+
+
+
+
+ + +{% endblock %} + +{% block jscripts %} + {{ super() }} + + +{% endblock %} diff --git a/pagure/templates/group_list.html b/pagure/templates/group_list.html new file mode 100644 index 0000000..2747332 --- /dev/null +++ b/pagure/templates/group_list.html @@ -0,0 +1,157 @@ +{% extends "master.html" %} + +{% block title %}Groups{% endblock %} +{% set tag = "groups" %} + +{% from "_browseheader.html" import browse_header %} + +{% block header %} + +{% endblock %} + +{% block content %} +
+
+ {{browse_header(select=tag)}} +
+
+
+

+ Groups {{ groups | length }} + {% if authenticated and config.get('ENABLE_GROUP_MNGT', False) %} + + + + {% endif %} +

+ + {% if authenticated %} + + {% endif %} + + {% if total_page and total_page > 1 %} + + {% endif %} + +
+ {% if groups %} + {% for group in groups %} +
+
+
+ +
{{ group.group_name }}
+
+
+ Formed {{ group.created |humanize }} +
+ +
+
+ {% endfor %} + {% else %} +

No groups have been created on this pagure instance yet

+ {% endif %} +
+
+{% endblock %} + +{% block jscripts %} + {{ super() }} + + +{% endblock %} diff --git a/pagure/templates/index.html b/pagure/templates/index.html new file mode 100644 index 0000000..cb97ec6 --- /dev/null +++ b/pagure/templates/index.html @@ -0,0 +1,102 @@ +{% extends "master.html" %} + +{% block title %}Home{% endblock %} +{% set tag = "home" %}i + +{% from "_render_repo.html" import render_repos, render_user_repos%} +{% from "_browseheader.html" import browse_header %} + + +{% block header %} + +{% endblock %} + +{% block content %} +
+
+ {{browse_header(select=select)}} +
+
+
+ {{ render_repos( + repos, total_page, 'page', page, + 'All Projects', repos_length, 'repos', username, sorting=sorting) }} +
+ +{% endblock %} + +{% block jscripts %} + {{ super() }} + + + + +{% if username %} + +{% endif %} + + + +{% endblock %} diff --git a/pagure/templates/index_auth.html b/pagure/templates/index_auth.html new file mode 100644 index 0000000..7f87988 --- /dev/null +++ b/pagure/templates/index_auth.html @@ -0,0 +1,207 @@ +{% extends "master.html" %} + +{% block title %}Home{% endblock %} +{% set tag = "projects" %} +{% from "_browseheader.html" import browse_header %} + + +{% block header %} + +{% endblock %} + + +{% from "_render_repo.html" import render_repos_as_card, render_contributions_graph %} + +{% block content %} +
+
+
+
+
+
+ My Projects {{repos | count}} + {% if config.get('ENABLE_NEW_PROJECTS', True) and repos %} + + + + + + {% endif %} +
+ {% if repos %} +
+ {% for repo in repos %} +
+ {% if repo.is_fork %} + {% set url = url_for('view_repo', username=repo.user.username, repo=repo.name) %} + {% else %} + {% set url = url_for('view_repo', repo=repo.name) %} + {% endif %} + + {% if repo.avatar_email %} +   + {% else %} + + {% endif %} + {{ repo.name }} + +
+ {% if config.get('ENABLE_TICKETS', True) and repo.settings.get('issue_tracker', True) %} + {% if repo.open_tickets_public == 0 %} + + + {{- repo.open_tickets_public }} + + {% else %} + + + {{- repo.open_tickets_public }} + + {% endif %} + {% endif %} + + {% if repo.open_requests == 0 %} + + + {{- repo.open_requests}} + + {% else %} + + + {{- repo.open_requests}} + + {% endif %} + +
+
+ {% endfor %} +
+ {% else %} +
+
You have no projects
+
+ {% if config.get('ENABLE_NEW_PROJECTS', True) %} + + Create a Project + + {% endif %} +
+
+ {% endif %} +
+ +
+
+ My Forks {{forks | count}} +
+ {% if forks %} +
+ {% for fork in forks %} +
+ {% if fork.is_fork %} + {% set url = url_for('view_repo', username=fork.user.username, repo=fork.name) %} + {% else %} + {% set url = url_for('view_repo', repo=fork.name) %} + {% endif %} + + + {{username}}/{{ fork.name }} + +
+ forked from + + {{fork.parent.name}} + + {% if fork.settings.get('issue_tracker', True) %} + {% if fork.open_tickets_public == 0 %} + + + {{- fork.open_tickets_public }} + + {% else %} + + + {{- fork.open_tickets_public }} + + {% endif %} + {% endif %} + {% if fork.settings.get('pull_requests', True) %} + {% if fork.open_requests == 0 %} + + + {{- fork.open_requests}} + + {% else %} + + + {{- fork.open_requests}} + + {% endif %} + {% endif %} +
+
+ {% endfor %} +
+ {% else %} +
+

You have no forks

+
+ {% endif %} +
+ + {{render_contributions_graph(username)}} + +
+
+
+
+ My Groups {{ user.groups | length }} +
+ {% for group in user.groups %} + + {% else %} +
+

No group found

+
+ {% endfor %} +
+
+
+
+
+{% endblock %} + +{% block jscripts %} + {{ super() }} + +{% endblock %} diff --git a/pagure/templates/issue.html b/pagure/templates/issue.html new file mode 100644 index 0000000..997a59c --- /dev/null +++ b/pagure/templates/issue.html @@ -0,0 +1,696 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" + import render_field, render_bootstrap_field, + show_comment, show_initial_comment %} + +{% block title %}Issue #{{ issueid }}: {{issue.title | noJS(ignore="img") | safe }} - {{ repo.name }}{% endblock %} +{% set tag = "home"%} + +{% block header %} + + + +{% endblock %} + +{% block repo %} +
+
+ {{ form.csrf_token }} + +
+

+ #{{ issueid }} + {{issue.title | noJS("img") | safe }} + {% if issue.private %} + + {% endif %} +

+ +
+ Created + {{ issue.date_created | humanize}} by {{ issue.user.user }} +
+ + {{ show_initial_comment(issue, username, repo,issueid, repo_admin, form) }} + +
+ {% if issue.comments %} + {% for comment in issue.comments %} + {% if comment.notification %} +
+
+
+
+ {{ + comment.date_created | humanize }} +
+ {{ comment.comment | markdown | noJS | safe }} +
+
+
+ {% else %} + {{ show_comment(comment, comment.id, repo, username, issueid, form, repo_admin) }} + {% endif %} + {% endfor %} + {% endif %} +
+ +
+
+ +
+ {% if authenticated and form %} + +
+ + + Preview + + + + +
+
+
+ + Select files OR drag them into the comment field below. + + + Markdown Syntax +
+ + +
+ {% else %} +

+ Login + to comment on this ticket. +

+ {% endif %} +
+ +
+
+
+ +
+
+
+ + + + {% if authenticated and repo_admin %} + {{ render_bootstrap_field(form.status, + formclass="issue-metadata-form") }} + {% endif%} + + + + {% if authenticated and repo_admin %} + + {% endif%} + + + + {% if authenticated and repo_admin %} + + {% endif%} + + + + {% if authenticated and repo_admin %} + + {% endif%} + + + + + {% if authenticated and repo_admin %} + + {% endif%} + + {% if repo.priorities %} + + {% if authenticated and repo_admin %} + {{ render_bootstrap_field(form.priority, + formclass="issue-metadata-form") }} + {% endif%} + + {% endif %} + + + + {% if authenticated and repo_admin %} + + {% endif %} +
+
+
+ +
+
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + + + + + + + + + + + +{% if config['EVENTSOURCE_SOURCE'] and not issue.private %} + + + +{% endif %} + +{% endblock %} diff --git a/pagure/templates/issues.html b/pagure/templates/issues.html new file mode 100644 index 0000000..0a58930 --- /dev/null +++ b/pagure/templates/issues.html @@ -0,0 +1,219 @@ +{% extends "repo_master.html" %} + +{% block title %}Issues - {{ repo.name }}{% endblock %} +{% set tag = "home"%} + + +{% block repo %} + + +

+ {% if status and status != 'Open' %} + {{ issues|count }} Closed Issues + {% else %} + {{ issues|count }} Open Issues + {% endif %} + + {% if repo.milestones %} + + Roadmap + + {% endif %} + + New Issue + + +

+ {% if oth_issues %} +
+ {% if (issues | length + oth_issues) %} + + {{ (100.0 * (1 - issues | length / (issues | length + oth_issues)))|round|int }}% + + {% endif %} +
+ {% endif %} + +
+ + {% if status and status != 'Open' %} + Open + Closed + {% else %} + Open + Closed + {% endif %} + + + + {% for tag in tag_list %} + {% if tag.tag in tags %} + {% if status and status != 'Open' %} + + {% else %} + + {% endif %} + {% else %} + + {% endif %} + + {{ tag.tag }} + {% endfor %} + +
+
+ + + + + + {% if status and status|lower != 'open' %} + + {% endif %} + + + {% if not status or status|lower == 'open' %} + + {% endif %} + + + + + + {% for issue in issues %} + + + + {% if status and status|lower != 'open' %} + + {% endif %} + + {% if not status or status|lower == 'open' %} + + {% endif %} + + + {% else %} + + + + {% endfor %} + +
IssueOpenedClosed + Priority (reset) + + Reporter (reset) + + Assignee (reset) +
+ #{{ issue.id }} + {% if status and status != 'Open' %} + {{issue.status}} + {% endif %} + {% if issue.private %} + + {% endif %} + + {{ issue.title | noJS("img") | safe }} + +    + {% if issue.user_comments|count > 0 %} + + + {{issue.user_comments|count}} + + {% endif %} + {% for tag in issue.tags%} + {{tag.tag}} + {% endfor%} + + {{ + issue.date_created | humanize}} + + {% if issue.closed_at != None %} + {{ + issue.closed_at | humanize}} + {% endif %} + + {% if issue.priority %} + + {{ + repo.priorities[issue.priority | string] }} + + {% endif %} + + + {{ issue.user.default_email | avatar(16) | safe }} + {{ issue.user.user }} + + + {% if issue.assignee %} + + {{ issue.assignee.default_email | avatar(16) | safe }} + {{ issue.assignee.user }} + + {% else %} + unassigned + {% endif %} +
No issues found
+
+{% endblock %} +{% block jscripts %} +{{ super() }} + + + +{% endblock %} diff --git a/pagure/templates/login/login.html b/pagure/templates/login/login.html new file mode 100644 index 0000000..7ceea3a --- /dev/null +++ b/pagure/templates/login/login.html @@ -0,0 +1,42 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}Login{% endblock %} +{% set tag = "home" %} + +{% block content %} + +
+
+
+
+
+ Login +
+
+
+ + {{ render_bootstrap_field(form.username) }} + {{ render_bootstrap_field(form.password) }} + + + {{ form.csrf_token }} +
+
+
+

+ If you do not have already an account: + Create one! +

+

+ + Forgot your password? +

+ +
+
+
+
+
+ +{% endblock %} diff --git a/pagure/templates/login/password_change.html b/pagure/templates/login/password_change.html new file mode 100644 index 0000000..bef3946 --- /dev/null +++ b/pagure/templates/login/password_change.html @@ -0,0 +1,29 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}Lost password{% endblock %} +{% set tag = "home" %} + +{% block content %} + +
+
+
+
+
+ Lost password +
+
+
+ {{ render_bootstrap_field(form.username) }} + + + {{ form.csrf_token }} +
+
+
+
+
+
+ +{% endblock %} diff --git a/pagure/templates/login/password_recover.html b/pagure/templates/login/password_recover.html new file mode 100644 index 0000000..ceab037 --- /dev/null +++ b/pagure/templates/login/password_recover.html @@ -0,0 +1,33 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}Change password{% endblock %} +{%block tag %}home{% endblock %} + +{% block content %} + +
+
+
+
+
+ Change password +
+
+
+ {{ render_bootstrap_field(form.old_password) }} + {{ render_bootstrap_field(form.password) }} + {{ render_bootstrap_field(form.confirm_password) }} + {{ form.csrf_token }} + + +
+
+
+
+
+
+ +{% endblock %} diff --git a/pagure/templates/login/password_reset.html b/pagure/templates/login/password_reset.html new file mode 100644 index 0000000..4cff01f --- /dev/null +++ b/pagure/templates/login/password_reset.html @@ -0,0 +1,29 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}Change password{% endblock %} +{% set tag = "home" %} + +{% block content %} +
+
+
+
+
+ Change password +
+
+
+ {{ render_bootstrap_field(form.password) }} + {{ render_bootstrap_field(form.confirm_password) }} + + + {{ form.csrf_token }} +
+
+
+
+
+
+{% endblock %} diff --git a/pagure/templates/login/user_new.html b/pagure/templates/login/user_new.html new file mode 100644 index 0000000..be1a9e0 --- /dev/null +++ b/pagure/templates/login/user_new.html @@ -0,0 +1,36 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}New user{% endblock %} +{% set tag = "home" %} + +{% block content %} +
+
+
+
+
+ Create new User +
+
+
+ {{ render_bootstrap_field( + form.user, field_description="username of the user") }} + {{ render_bootstrap_field( + form.fullname, field_description="full name of the user") }} + {{ render_bootstrap_field( + form.email_address, field_description="email address of the user") }} + {{ render_bootstrap_field( + form.password, field_description="password of the user") }} + {{ render_bootstrap_field( + form.confirm_password, field_description="confirm the password") }} + + + {{ form.csrf_token }} +
+
+
+
+
+
+{% endblock %} diff --git a/pagure/templates/master.html b/pagure/templates/master.html new file mode 100644 index 0000000..5b3fe98 --- /dev/null +++ b/pagure/templates/master.html @@ -0,0 +1,162 @@ + + + + + {% block title %}{% endblock %} - Pagure + + + + + + + {% block header %}{% endblock %} + + + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+ +
+ + {% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} +
+
+ {% for category, message in messages %} + + {% endfor %} +
+
+ {% endif %} + {% endwith %} + + {% block content %}{% endblock %} +
+ + + + {% block jscripts %} + + + + + {% endblock %} + +{% if config['FEDMENU_URL'] %} + + + +{% endif %} + + + diff --git a/pagure/templates/new_issue.html b/pagure/templates/new_issue.html new file mode 100644 index 0000000..642019b --- /dev/null +++ b/pagure/templates/new_issue.html @@ -0,0 +1,225 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}{% if not type or type == 'new' + %}New issue{% elif type and type == 'edit' + %}Edit issue #{{ issueid }} {% endif %} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block header %} + +{% endblock %} + + +{% block repo %} +
+
+
+
+ {% if not type or type == 'new' %} + New issue + {% elif type and type == 'edit' %} + Edit issue #{{ issueid }} + {% endif %} +
+
+ {% if not type or type == 'new' %} +
+ + {% elif type and type == 'edit' %} + + {% endif %} + {{ render_bootstrap_field(form.title, field_description="Gist of your issue") }} + {% if type == 'edit' %} + {{ render_bootstrap_field(form.status, field_description="bug status") }} + {% endif %} + {% if types %} +
+ + +
+ Ticket type +
+
+ {% endif %} + {{ render_bootstrap_field(form.private, field_description="Do you want to keep the issue private?") }} +
+ + + Preview + + +
+ + {% if form.issue_content.errors %} + + + {% for error in form.issue_content.errors %} + {{ error }}  + {% endfor %} + + + {% endif %} +
+
+
+
+ Attach file + +

+ {% if not type or type == 'new' %} + + {% elif type and type == 'edit' %} + + {% endif %} + + {{ form.csrf_token }} + Markdown Syntax +

+
+
+
+ + {% if authenticated and repo_admin and type and type == 'edit' %} +
+ + {{ form.csrf_token }} +
+ {% endif %} + +
+
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + + + +{% endblock %} diff --git a/pagure/templates/new_project.html b/pagure/templates/new_project.html new file mode 100644 index 0000000..cf9a920 --- /dev/null +++ b/pagure/templates/new_project.html @@ -0,0 +1,32 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}New project{% endblock %} +{% set tag = "new_project" %} + + +{% block content %} +
+
+
+
+
+ Create new Project +
+
+
+ {{ render_bootstrap_field(form.name, field_description="the name of your project") }} + {{ render_bootstrap_field(form.description, field_description="short description of the project") }} + {{ render_bootstrap_field(form.url, field_description="url of the project's website") }} + {{ render_bootstrap_field(form.avatar_email, field_description="libravatar email address avatar email") }} + {{ render_bootstrap_field(form.create_readme, field_description="Create a README file automatically") }} + + + {{ form.csrf_token }} +
+
+
+
+
+
+{% endblock %} diff --git a/pagure/templates/new_release.html b/pagure/templates/new_release.html new file mode 100644 index 0000000..5c11a25 --- /dev/null +++ b/pagure/templates/new_release.html @@ -0,0 +1,33 @@ +{% extends "repo_master.html" %} + +{% block title %}New release - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block repo %} + +

Upload a new release

+ +

+ You can upload your tarball to pagure. This tarball can be as big as + {{ config['MAX_CONTENT_LENGTH'] / 1024 /1024 }}MB. +

+ +
+{{ form.csrf_token }} + + +
+ +
+
+ +

+ After the upload the source will be available in the + + release folder + . +

+ +{% endblock %} diff --git a/pagure/templates/not_found.html b/pagure/templates/not_found.html new file mode 100644 index 0000000..cffd00f --- /dev/null +++ b/pagure/templates/not_found.html @@ -0,0 +1,25 @@ +{% extends "master.html" %} + +{% block title %}Page not found :'({% endblock %} +{% set tag = "error" %} + + +{% block content %} +
+
+
+

Page not found (404)

+ {% if error and error.description %} +

With the message:

+
+

{{ error.description }}

+
+ {% else %} +

The requested page cannot be found, we're sorry :(

+ {% endif %} +

Maybe you enter a bad URL, or the page has moved or has been removed, or...
+ Use the main navigation menu to get (re)started.

+
+
+
+{% endblock %} diff --git a/pagure/templates/plugin.html b/pagure/templates/plugin.html new file mode 100644 index 0000000..0d6d3ff --- /dev/null +++ b/pagure/templates/plugin.html @@ -0,0 +1,32 @@ +{% from "_formhelper.html" import render_field_in_row %} + +{% if full %} +{% extends "repo_master.html" %} + +{% block title %}{{ select.capitalize() }} {{ plugin.name }} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} +{% endif %} + + +{% block repo %} +{% if full %} +

{{ plugin.name }} settings

+{% endif %} + +
+ {{ plugin.description | markdown | noJS | safe }} + + {% for field in fields %} + {{ render_field_in_row(field) }} + {% endfor %} +
+

+ + + {{ form.csrf_token }} +

+
+ +{% endblock %} diff --git a/pagure/templates/pull_request.html b/pagure/templates/pull_request.html new file mode 100644 index 0000000..4cb28d2 --- /dev/null +++ b/pagure/templates/pull_request.html @@ -0,0 +1,1272 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row, show_comment, render_bootstrap_field %} + +{% block title %} + {%- if pull_request -%} + PR#{{ requestid }}: {{ pull_request.title | noJS(ignore="img")}} - {{ repo.name }} + {% elif form and (repo_admin or remote_git) %} + Create new Pull Request for {{ branch_to }} - {{ repo.name }} + {%- elif origin == 'compare_commits' -%} + Diff from {{ commit1 }} to {{ commit2 }} - {{ repo.name }} + {%- else -%} + Diff from {{ branch_from }} to {{ branch_to }} - {{ repo.name }} + {%- endif -%} +{% endblock %} + +{% set tag = "home" %} + +{% block header %} + + + +{% endblock %} + +{% block repo %} +
+
+{% if pull_request %} +

PR#{{requestid}} + {% if pull_request.status != 'Open'%} + {{pull_request.status}} + {% endif %} + {{ pull_request.title | noJS(ignore="img") | safe}} + {% if authenticated and (g.fas_user.username == pull_request.user.username + or repo_admin) and pull_request.status == 'Open'%} + + {% if pull_request.status == 'Open' and authenticated and + (repo_admin or g.fas_user.username == pull_request.user.username) %} +
+ {% endif %} +
+ + {% if pull_request.status == 'Open' and authenticated and + (repo_admin or g.fas_user.username == pull_request.user.username) %} + {{ mergeform.csrf_token }} + + {% endif %} +
+ {% if pull_request.status == 'Open' and authenticated and + (repo_admin or g.fas_user.username == pull_request.user.username) %} +
+ {% endif %} +
+ {% endif %} +

+ +
+ Proposed {{ pull_request.date_created |humanize }} + by {{ pull_request.user.default_email | avatar(16) | safe + }} {{ pull_request.user.user }} +
+ + From + {{ + pull_request.project_from.fullname or pull_request.remote_git + }} + + + {{ pull_request.branch_from }} + + +  into + {{ pull_request.project.fullname }} + + + {{ pull_request.branch }} + + +{%if pull_request and pull_request.initial_comment %} +
+
+{{ pull_request.initial_comment | markdown | noJS | safe }} +
+
+{%endif%} +{% elif form and (repo_admin or remote_git) %} +

Create pull request

+{% else %} +

Diff + (tree)

+ + {% if origin == 'compare_commits' %} +
{{ commit1 }} .. {{ commit2 }}
+ {% endif %} + +{% endif %} + +{% if form and (repo_admin or remote_git) %} +
+ {% if remote_git %} +
+ + + {% else %} + + {% endif %} +
+ Pull from + {% + if remote_git -%}{{ remote_git }}{%- + else -%} + {{ repo.fullname }} {%- + endif -%} + + + + {{ branch_from }} + + into + + {% if repo.is_fork -%}{{ repo.parent.fullname }}{% + else %}{{ repo.fullname }}{% endif %} +   + +
+ {% if contributing %} +
+ {{ contributing | markdown | noJS | safe}} +
+ {% endif %} + {{ render_bootstrap_field(form.title) }} +
+ + + Preview + + {{ form.initial_comment(class_="form-control")|safe }} +
+ + {% if form.initial_comment.errors %} + + + {% for error in form.initial_comment.errors %} + {{ error }}  + {% endfor %} + + + {% endif %} +
+ +
+

+ + {{ form.csrf_token }} + + Cancel + +

+
+
+{% endif %} + + + +
+ + + +
+ {% if authenticated and pull_request %} +
+ {% endif %} + + {% if diff %} + {% set count = 0 %} + {% for patch in diff %} + {% set count = count+1 %} + {% if patch |hasattr('new_id') %} + {% set patch_new_id = patch.new_id %} + {% elif patch |hasattr('delta') %} + {% set patch_new_id = patch.delta.new_file.id %} + {% else %} + {% set patch_new_id = patch.new_oid %} + {% endif %} + + {% if patch |hasattr('old_id') %} + {% set patch_old_id = patch.old_id %} + {% elif patch |hasattr('delta') %} + {% set patch_old_id = patch.delta.old_file.id %} + {% else %} + {% set patch_old_id = patch.old_oid %} + {% endif %} + + {% if patch | hasattr('new_file_path') %} + {% set patch_new_file_path = patch.new_file_path -%} + {% if patch.new_file_path != patch.old_file_path %} + {% set patch_old_file_path = patch.old_file_path %} + {%- endif -%} + {%- elif patch | hasattr('delta') -%} + {% set patch_new_file_path = patch.delta.new_file.path -%} + {%- if patch.delta.new_file.path != patch.delta.old_file.path -%} + {% set patch_old_file_path = patch.delta.old_file.path %} + {%- endif -%} + {%- endif -%} + + +
+
+
+
+ {% if patch | hasattr('additions') %} + {# Version of pygit2 -0.21.4 -- F21/EL7 #} + {% set linesadded = patch.additions %} + {% set linesremoved = patch.deletions %} + {% else %} + {# Version of pygit2 -0.23.0 -- F23 #} + {% set linesadded = patch.line_stats[1] %} + {% set linesremoved = patch.line_stats[2] %} + {% endif %} + + {% macro lineschanged(added, removed) -%} + {%if added%} + +{{linesadded}} + {%endif%} + {%if removed%} + -{{linesremoved}} + {%endif%} + {%endmacro%} + + {% macro viewfilelink(filepath, identifier=False)%} + {% if pull_request %} + {% if not identifier %} + {% set identifier = pull_request.branch_from %} + {% endif %} + {{filepath | unicode}} + {% endmacro %} + + {% if patch | hasattr('new_file_path') %} + {%- if patch.new_file_path == patch.old_file_path -%} + {%- if patch.status == 'D' -%} + {% set patchtype = "removed"%} +
+ {{ viewfilelink(patch.new_file_path) }} {{ lineschanged(False, True) }} +
+
file removed
+ {%-elif patch.status == 'A' -%} + {% set patchtype = "added"%} +
+ {{ viewfilelink(patch.new_file_path) }} {{ lineschanged(True, False) }} +
+
file added
+ {%-elif patch.status == 'M' -%} + {% set patchtype = "changed"%} +
+ {{ viewfilelink(patch.new_file_path) }} {{ lineschanged(True, True) }} +
+
file changed
+ {%-endif-%} + {%- else -%} + {% set patchtype = "moved"%} + {{lineschanged(True, True)}} +
+ {{patch.old_file_path}}
+ {{viewfilelink(patch.new_file_path)}} +
+
file moved
+ {%- endif -%} + {%- elif patch | hasattr('delta') -%} + {%- if patch.delta.new_file.path == patch.delta.old_file.path -%} + {%- if patch.delta.new_file.mode == 0 and patch.delta.old_file.mode == 33188 -%} + {% set patchtype = "removed"%} +
{{viewfilelink(patch.delta.new_file.path)}} {{lineschanged(False, True)}}
+
file removed
+ {%-elif patch.delta.new_file.mode == 33188 and patch.delta.old_file.mode == 0 -%} + {% set patchtype = "added"%} +
{{viewfilelink(patch.delta.new_file.path)}} {{lineschanged(True, False)}}
+
file added
+ {%-elif patch.delta.new_file.mode == 33188 and patch.delta.old_file.mode == 33188 -%} + {% set patchtype = "changed"%} +
+ {{ viewfilelink(patch.delta.new_file.path)}} {{lineschanged(True, True)}} +
+
file changed
+ {%-endif-%} + {%- else -%} + {% set patchtype = "moved"%} + {{lineschanged(True, True)}} +
+ {{patch.delta.old_file.path}}
+ {{viewfilelink(patch.delta.new_file.path)}} +
+
file moved
+ {%- endif -%} + {%- endif -%} +
+
+ {% if patchtype == "moved" and linesadded == 0 and linesremoved == 0%} +
+
file was moved with no change to the file
+
+ {% elif patchtype == "added" and linesadded == 0 %} +
+
empty file added
+
+ {% else %} + {% if patchtype == "added" and linesadded > 1000 %} +
+
+ The added file is too large to be shown here, see it at: + {{ viewfilelink(patch_new_file_path) }} +
+
+ {% elif patchtype == "removed" %} +
+
+ The removed file is too large to be shown here, see it at: + {{ viewfilelink(patch_new_file_path, patch_old_id) }} +
+
+ {% else %} + {% autoescape false %} + {{ patch | patch_to_diff | html_diff | format_loc( + filename=patch_new_file_path, + commit=patch_new_id, + prequest=pull_request, + index=loop.index, + tree_id=diff_commits[0].tree.id)}} + {% endautoescape %} + {% endif %} + {% endif %} + +
+
+ {% endfor %} + {% endif %} + + {% if authenticated and pull_request %} + {{ mergeform.csrf_token }} +
+ {% endif %} +
+ {% if pull_request %} +
+
+ {% if pull_request.comments %} +
+ + {% for comment in pull_request.comments %} + {% if comment.commit_id %} +
+
+
+ {{ comment.user.user }} commented on line + {{ + comment.line }} of {{ comment.filename }}. +
+ {{ + comment.date_created | humanize }}
+
+ (Show) +
+ {{ comment.comment }} +
+
+
+ {% elif comment.notification %} +
+
+
+
+ {{ + comment.date_created | humanize }} +
+ {{ comment.comment | markdown | noJS | safe }} +
+
+
+ {% else %} + {{ show_comment(comment, comment.id, repo, username, + requestid, form, repo_admin) }} + {% endif %} + {% endfor %} + {{ mergeform.csrf_token }} +
+ {% endif %} +
+ + {% if authenticated and mergeform and pull_request %} +
+
+ +
+ + {% if authenticated %} +
+ {{ mergeform.csrf_token }} + +
+ + + Preview + + + +
+
+
+ + Markdown Syntax +
+ + +
+
+ {% else %} +

+ + Login + to comment on this ticket. +

+ {% endif %} +
+
+
+ {% endif %} +
+ {% endif %} +
+ +
+ + +
+ {% if pull_request %} +
+ + + {% if pull_request.status != 'Open'%} + + {% endif %} + + {% if pull_request.flags %} +
+
    + {% for flag in pull_request.flags %} +
  • +
    + {{ flag.username }} +
    {{ flag.percent }}%
    +
    +
    + {{ flag.comment }} +
    + {{ flag.date_created | humanize }}
    +
    +
    +
  • + {% endfor %} +
+
+ {% endif %} + +
+
+ {% if authenticated and mergeform and pull_request.status == 'Open' and repo_admin %} +
+ + {% endif %} + + {% if authenticated and mergeform and pull_request.status == 'Open' and repo_admin %} + + +
+ + {% endif %} +
+
+
+ {% endif %} + {% if diff and pull_request%} +
+
+
Changes summary
+
+ {% macro changeschangedfile(filepath, added, removed, diffanchor) -%} +
+
+ +{{added}} + -{{removed}} +
+
+ file changed +
+
+ + {%- endmacro %} + {% macro changesrenamedfile(oldfilepath, newfilepath, added, removed, diffanchor) -%} +
+
+{{added}} -{{removed}}
+
file renamed
+
+
+ {{oldfilepath}}
+ {{newfilepath}} +
+ {%- endmacro %} + {% macro changesdeletedfile(filepath, added, removed, diffanchor) -%} +
+
-{{removed}}
+
file removed
+
+ + {%- endmacro %} + {% macro changesaddedfile(filepath, added, removed, diffanchor) -%} +
+
+{{added}}
+
file added
+
+ + {%- endmacro %} +
+ {% set count = 0 %} + {% for patch in diff %} + {% set count = count+1 %} +
+ + {% if patch | hasattr('additions') %} + {# Version of pygit2 -0.21.4 -- F21/EL7 #} + {% set linesadded = patch.additions %} + {% set linesremoved = patch.deletions %} + {% else %} + {# Version of pygit2 -0.23.0 -- F23 #} + {% set linesadded = patch.line_stats[1] %} + {% set linesremoved = patch.line_stats[2] %} + {% endif %} + {% if patch | hasattr('new_file_path') %} + {%- if patch.new_file_path == patch.old_file_path -%} + {%- if patch.status == 'D' -%} + {{ changesdeletedfile(patch.new_file_path, linesadded, linesremoved, count) }} + {%-elif patch.status == 'A' -%} + {{ changesaddedfile(patch.new_file_path, linesadded, linesremoved, count) }} + {%-elif patch.status == 'M' -%} + {{ changeschangedfile(patch.new_file_path, linesadded, linesremoved, count) }} + {%-endif-%} + {%- else -%} + {{changesrenamedfile(patch.old_file_path, patch.new_file_path, linesadded, linesremoved, count)}} + {%- endif -%} + {%- elif patch | hasattr('delta') -%} + {%- if patch.delta.new_file.path == patch.delta.old_file.path -%} + {%- if patch.delta.new_file.mode == 0 and patch.delta.old_file.mode == 33188 -%} + {{ changesdeletedfile(patch.delta.new_file.path, linesadded, linesremoved, count) }} + {%-elif patch.delta.new_file.mode == 33188 and patch.delta.old_file.mode == 0 -%} + {{ changesaddedfile(patch.delta.new_file.path, linesadded, linesremoved, count) }} + {%-elif patch.delta.new_file.mode == 33188 and patch.delta.old_file.mode == 33188 -%} + {{ changeschangedfile(patch.delta.new_file.path, linesadded, linesremoved, count) }} + {%-endif-%} + {%- else -%} + {{changesrenamedfile(patch.delta.old_file.path, patch.delta.new_file.path, linesadded, linesremoved, count)}} + {%- endif -%} + {%- endif -%} +
+ {% endfor %} +
+
+ {% endif %} +
+ +
+{% endblock %} + +{% block jscripts %} +{{ super() }} + + + + + + + + + + + +{% if config['EVENTSOURCE_SOURCE'] and pull_request %} + + + +{% endif %} + +{% endblock %} diff --git a/pagure/templates/pull_request_comment.html b/pagure/templates/pull_request_comment.html new file mode 100644 index 0000000..f3f6f2b --- /dev/null +++ b/pagure/templates/pull_request_comment.html @@ -0,0 +1,36 @@ +{% if authenticated %} + +
+ +
+ + +
+ +
+ +
+ +
+ + {{ form.csrf_token }} +
+ + +
+ + + +
+ +{% else %} +

Login to comment on this ticket.

+{% endif %} diff --git a/pagure/templates/pull_request_title.html b/pagure/templates/pull_request_title.html new file mode 100644 index 0000000..c7fbd6d --- /dev/null +++ b/pagure/templates/pull_request_title.html @@ -0,0 +1,50 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}Edit PR#{{ request.id }}: {{ request.title }} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block header %} + + +{% endblock %} + +{% block repo %} + +
+
+
+
+ Edit PR#{{ request.id }}: {{ request.title }} - {{ repo.name }} +
+
+
+ + + {{ render_bootstrap_field( + form.title, + field_description="the new title of your pull-request") }} + {{ render_bootstrap_field( + form.initial_comment, + field_description="description of your pull-request") }} +
+

+ + {{ form.csrf_token }} + + + + {{ form.csrf_token }} +

+
+
+
+
+
+ +{% endblock %} diff --git a/pagure/templates/releases.html b/pagure/templates/releases.html new file mode 100644 index 0000000..dba6e9d --- /dev/null +++ b/pagure/templates/releases.html @@ -0,0 +1,65 @@ +{% extends "repo_master.html" %} + +{% block title %}Releases - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block repo %} + +

+ Releases +{% if config.get('UPLOAD_FOLDER_PATH') and config.get('UPLOAD_FOLDER') %} + {% if repo_admin %} + + + + {% endif %} +{% endif %} +

+ +{% if config.get('UPLOAD_FOLDER_PATH') and config.get('UPLOAD_FOLDER') %} +

+ If the developers have upload one or more tarball(s), you will be able to + find them in the + release folder. + +

+{% endif %} + +
+ {% if tags %} + + {% else %} +

+ This project has not been tagged. +

+ {% endif %} +
+ +{% endblock %} diff --git a/pagure/templates/remote_pull_request.html b/pagure/templates/remote_pull_request.html new file mode 100644 index 0000000..4489ddb --- /dev/null +++ b/pagure/templates/remote_pull_request.html @@ -0,0 +1,59 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% block title %}Remote Pull request {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block repo %} + + +

New remote pull-request

+ +

+ Remote pull-requests are pull-requests made from a project *not* hosted + on pagure or not hosted on the same pagure instance than the target project, + hosted on pagure. +

+ +

+ If you wish to create a pull-request from a fork to a project on pagure, + visit your fork, go to the branch where you pushed your changes, it will + show the commits that can be merged with a red asterisk and click on the + "Pull-Request" button. +

+ +{% if form %} +
+
+ + {{ render_field_in_row(form.title, size=80) }} + {{ render_field_in_row(form.git_repo, size=80) }} + {{ render_field_in_row(form.branch_from, size=80) }} + + + + +
To branch + +
+

+ + {{ form.csrf_token }} + + + +

+
+
+{% endif %} + +{% endblock %} + diff --git a/pagure/templates/repo_info.html b/pagure/templates/repo_info.html new file mode 100644 index 0000000..f65c3fc --- /dev/null +++ b/pagure/templates/repo_info.html @@ -0,0 +1,322 @@ +{% extends "repo_master.html" %} + +{% block title %}{{ select.capitalize() }} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block repo %} +
+ {% if repo_obj and repo_obj.is_empty %} +
+ +
+ {% else %} +
+ {% if readme %} +
+ {% if safe %} + {{ readme | noJS |safe }} + {% else %} + {{ readme | noJS }} + {% endif %} +
+ {% else %} +
+
+
+ The {{repo.name}} project has not checked in a README file yet +
+
+
+ {% endif %} +
+ {% endif %} +
+
+
+
Owners
+ +
Branches
+
+ {% if head %} +
+
+ {% if branchname == head %} + + {{ head }} + {% else %} + + + {{ head }} + + {% endif %} +
+
+ +
+
+ {% endif %} + + {% for branch in branches if branch != head%} +
+
+ + {% if branchname != branch %} + {{ branch }} + + {% else %} + {{ branch }} + {% endif %} + +
+
+ {% if repo_admin and branch != head %} +
+ {{ form.csrf_token }} + + + +
+ {% endif %} +
+
+ {% endfor %} +
+
Source GIT URLs{% if + (authenticated and repo_admin) or + (config['DOC_APP_URL'] and repo and + repo.settings.get('project_documentation', True)) + %} + {%endif%}
+
+
+
+
SSH
+ +
+
+
+
+
GIT
+ +
+
+
+ {% if config['DOC_APP_URL'] + and repo + and repo.settings.get('project_documentation', True) %} +
Docs GIT URLs
+ {% if authenticated and repo_admin %} +
+
+
SSH
+ +
+
+ {% endif %} +
+
+
GIT
+ +
+
+ {% endif %} + {% if authenticated and repo_admin %} + {% if config.get('ENABLE_TICKETS', True) + and repo.settings.get('issue_tracker', True) %} +
Issues GIT URLs
+
+
+
SSH
+ +
+
+ {% endif %} +
Pull Requests GIT URLs
+
+
+
SSH
+ +
+
+ {% endif %} +
+
+
+
+ created {{repo.date_created|humanize}} +
+
+ + {% if last_commits %} + +
+ {% endif %} +
+ + + +{% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} diff --git a/pagure/templates/repo_master.html b/pagure/templates/repo_master.html new file mode 100644 index 0000000..9fd4e5e --- /dev/null +++ b/pagure/templates/repo_master.html @@ -0,0 +1,269 @@ +{% extends "master.html" %} + +{% block title %}{{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block content %} +
+
+
+

+ + {% if repo.is_fork -%} + + {{ repo.user.user }}/{{ repo.name}} + {%- else -%} + {{ repo.name }} + {%- endif -%} + + {% if authenticated and not repo.is_fork %} +
+
+ + {{ forkbuttonform.csrf_token }} +
+ {% endif %} + + {% if authenticated %} +
+ + + +
+ {% endif %} + {% if branchname %} +
+ + + +
+ {% endif %} +

+ {% if repo.is_fork %} +
+ Forked from + {% if repo.parent.is_fork %} + + {% else %} + + {% endif %} + {{ repo.parent.fullname }} + +
+ {% endif %} +
+ {% if repo.description %}{{ repo.description | safe }}{% else %}-{% endif -%} + {%- if repo.url %}  |  {{ repo.url }}{% endif %} +
+
+ +
+
+ +
+{% block repo %} +{% endblock %} +
+{% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} diff --git a/pagure/templates/requests.html b/pagure/templates/requests.html new file mode 100644 index 0000000..a9dc522 --- /dev/null +++ b/pagure/templates/requests.html @@ -0,0 +1,234 @@ +{% extends "repo_master.html" %} + +{% block title %}Pull requests - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + + +{% block repo %} + +
+

+ {% if status|lower != 'open' and status|lower != 'false' %} + {{ status }} {% elif status|lower != 'open' -%} + Closed/Merged {% endif -%} + Pull Requests {{ requests|count }} + {% if authenticated and repo.settings.get('pull_requests', True) %} + + + + + + {%endif%} +

+ + +
+
+ + + + + + {% if status|lower != 'open' %} + + {% endif %} + + {% if status|lower == 'open' %} + + {% endif %} + + + + + + {% for request in requests %} + + + + {% if status|lower not in ['open', 'true'] %} + + {% endif %} + + {% if status|lower in ['open', 'true'] %} + + {% endif %} + + {% else %} + + + + {% endfor %} + +
Pull RequestOpenedClosed + Reporter(reset) + + Assignee(reset) +
+ + PR#{{ request.id }} + {% if status|lower not in ['open', 'true'] %}{{request.status}}{% + endif %} {{ request.title | noJS("img") | safe }} + + {% if request.user_comments|count > 0 %} +    + + + {{ request.user_comments|count }} + + {% endif %} + + {{ + request.date_created | humanize}} + + {{ + request.closed_at | humanize + if request.closed_at }} + + + {{ request.user.default_email | avatar(16) | safe }} + {{ request.user.user }} + + + {% if request.assignee %} + + {{ request.assignee.default_email | avatar(16) | safe }} + {{ request.assignee.user }} + + {% else %} + unassigned + {% endif %} +
No Pull Requests found
+
+{% endblock %} + + + +{% block jscripts %} +{{ super() }} +{% if authenticated %} + +{% endif %} +{% endblock %} diff --git a/pagure/templates/roadmap.html b/pagure/templates/roadmap.html new file mode 100644 index 0000000..3fe4094 --- /dev/null +++ b/pagure/templates/roadmap.html @@ -0,0 +1,168 @@ +{% extends "repo_master.html" %} + +{% block title %}Roadmap - {{ repo.name }}{% endblock %} +{% set tag = "home"%} + + +{% block repo %} + + +

+ {{ issues|count }} Milestones + + + List + + + New Issue + + +

+ {% if oth_issues %} +
+ {% if (issues | length + oth_issues) %} + + {{ (100.0 * (1 - issues | length / (issues | length + oth_issues)))|round|int }}% + + {% endif %} +
+ {% endif %} + +
+ + {% if not status %} + Open + All + {% else %} + Open + All + {% endif %} + + + + {% for tag in tag_list %} + {% if tag.tag in tags %} + + {% else %} + + {% endif %} + + {{ tag.tag }} + {% endfor %} + +
+ +{% for milestone in milestones %} +
+
+
+ Milestone: {{ milestone }} + {% if repo.milestones[milestone] %} + + Due: {{ repo.milestones[milestone] }} + + {% endif %} +
+
+ +
+ + + + + {% for issue in issues[milestone] |sort(attribute='priority') %} + + + + + + + {% else %} + + + + {% endfor %} + +
+ #{{ issue.id }} + {% if status and status != 'Open' %} + {{issue.status}} + {% endif %} + {% if issue.private %} + + {% endif %} + + {{ issue.title | noJS("img") | safe }} + +    + {% if issue.comments|count > 0 %} + + + {{issue.comments|count}} + + {% endif %} + {% for tag in issue.tags%} + {{tag.tag}} + {% endfor%} + + {{ + issue.date_created | humanize}} + + {% if issue.priority %} + {{repo.priorities[issue.priority | string] }} + {% endif %} + + {% if issue.status != 'Open' %} + {{ issue.status }} + {% else %} + {% if issue.assignee %} + {{ issue.assignee.default_email | avatar(16) | safe }} + {{ issue.assignee.user }} + {% else %} + unassigned + {% endif %} + {% endif %} +
No issues found
+
+
+{% endfor %} +{% endblock %} +{% block jscripts %} +{{ super() }} + + + +{% endblock %} diff --git a/pagure/templates/settings.html b/pagure/templates/settings.html new file mode 100644 index 0000000..d68e456 --- /dev/null +++ b/pagure/templates/settings.html @@ -0,0 +1,671 @@ +{% extends "repo_master.html" %} +{% from "_formhelper.html" import render_field_in_row %} + +{% block title %}{{ select.capitalize() }} - {{ repo.name }}{% endblock %} +{% set tag = "home" %} + +{% block repo %} + +
+

Settings for {{repo.name}}

+
+
+
+
+ Project Details +
+
+
+
+ + + Short description of the project +
+
+ + + Website URL of the project +
+
+ + + Email address linked to avatar to display for the project +
+
+ + + tags for project +
+ + {{ form.csrf_token }} +
+
+
+
+
+
+
+ Default Branch +
+
+
+ {{ branches_form.csrf_token }} + {{ branches_form.branches(class_="c-select") }} + +
+
+
+
+ {% if config.get('WEBHOOK', False) %} +
+
+
+ Private web-hook key +
+
+

+ Each message sent to the web-hook are signed via hmac and SHA1 using + this private key. +

+

+ This key is private to your project, make sure to store in a safe place + and do not share it. +

+
+
+
+ +
+
+ +
+ + {{ form.csrf_token }} +
+
+
+
+ {% endif %} + +
+
+
+ API Keys +
+
+

+ API keys are tokens used to authenticate you on pagure. They can also + be used to grant access to 3rd party application to behave on this + project on your name. +

+

+ These are your personal tokens; they are not visible to other owners + of this repository. +

+

+ These keys are valid for 60 days. +

+

+ These keys are private to your project, make sure to store in a safe + place and do not share it. +

+
+ {% if repo.tokens %} +
    + {% for token in repo.tokens %} + {% if token.user.username == g.fas_user.username %} +
  • +
    +
    +
    + +
    +
    + {% if token.expired %} + Expired since {{ token.expiration.date() }} + {% else %} + Valid until: {{ token.expiration.date() }} +
    + + {{ form.csrf_token }} +
    + {% endif %} + + +
  • + {% endif %} + {% endfor %} +
+ {% endif %} + + +
+
+ +
+
+
+ Project options +
+
+
+ {% for key in repo.settings | sort %} + {% if not config.get('ENABLE_TICKETS', True) and key in ['issue_tracker'] %} + {% elif not config.get('DOC_APP_URL') and key in ['project_documentation'] %} + {% elif not config.get('WEBHOOK') and key in ['Web-hooks'] %} + {% else %} + {% if repo.settings[key] in [True, False, 'y'] %} +
+ +
+ {% else %} +
+ + +
+ {% endif %} + {% endif %} + {% endfor %} + +

+ + {{ form.csrf_token }} +

+
+
+
+
+ +
+
+
+ Re-generate git repos +
+
+
+ + + {{ form.csrf_token }} +
+
+ + + {{ form.csrf_token }} +
+
+
+
+ + {% if config.get('ENABLE_USER_MNGT', True) %} +
+
+
+ Users and Groups +
+
+ +

Below is the list of user having commit rights to this repo.

+ +

+ + add user + + + + add group + +

+
+ +
+
+ {% endif %} + + + {% if plugins %} +
+
+
+ Hooks +
+
+
+ {% set idcount = 1 %} + {% for plugin in plugins %} +
+ +
+ +
+
+ {% set idcount = idcount+1 %} + {% endfor %} +
+
+
+
+ {% endif %} + + + {% if config.get('ENABLE_TICKETS', True) + and repo.settings.get('issue_tracker', True) %} +
+
+
+ Priorities +
+
+

+ Below are the priorities you may assign to a ticket, allowing you + to sort them with it. The Weight determines the ordering. Higher + priority should correspond to higher weight. + + To remove an entry, simply clean the Weight and Title + +

+
+
+ {{ tag_form.csrf_token }} +
+
+
+ Weight +
+
+ Title +
+
+
+ {% for priority in repo.priorities | sort %} +
+
+ +
+
+ +
+
+ {% endfor %} +
+
+ + {% if not repo.priorities %} + + {% endif %} +
+
+
+ +
+
+
+
+
+
+ +
+
+
+ Roadmap +
+
+

+ Below are the milestones you may assign to a ticket, allowing + you to build a roadmap using the `roadmap` tag and a tag + corresponding to one of the milestones defined here. +

+
+
+ {{ tag_form.csrf_token }} +
+
+
+ Milestone +
+
+ Date (optional) +
+
+
+ {% for milestone in repo.milestones | sort %} +
+
+ +
+
+ +
+
+ {% endfor %} +
+ +
+
+ +
+
+
+
+
+
+ +
+
+
+ Issue Tags +
+
+

+ Here below is the list of tags associated with one or more issue of + the project. +

+
+ +
+
+ {% endif %} + + {% if config.get('ENABLE_DEL_PROJECTS', True) %} +
+
+
+ Delete Project +
+
+
+ +
+
+
+
+ {% endif %} + +
+
+ +{% endblock %} + +{% block jscripts %} +{{ super() }} + +{% endblock %} diff --git a/pagure/templates/unauthorized.html b/pagure/templates/unauthorized.html new file mode 100644 index 0000000..171c126 --- /dev/null +++ b/pagure/templates/unauthorized.html @@ -0,0 +1,18 @@ +{% extends "master.html" %} + +{% block title %}Unauthorized :'({% endblock %} +{% set tag = "error" %} + + +{% block content %} +
+
+
+

Unauthorized (401)

+

You are not authorized to display the requested page.

+

If you're not logged in, try to login; if you're already done, + that probably means you do not have sufficient access.

+
+
+
+{% endblock %} diff --git a/pagure/templates/user_emails.html b/pagure/templates/user_emails.html new file mode 100644 index 0000000..db28f04 --- /dev/null +++ b/pagure/templates/user_emails.html @@ -0,0 +1,26 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}Add email{% endblock %} +{% set tag = "home" %} + + +{% block content %} +
+
+
+
+ Add new email +
+
+
+ {{ render_bootstrap_field(form.email) }} + + + {{ form.csrf_token }} +
+
+
+
+
+{% endblock %} diff --git a/pagure/templates/user_info.html b/pagure/templates/user_info.html new file mode 100644 index 0000000..a4fe964 --- /dev/null +++ b/pagure/templates/user_info.html @@ -0,0 +1,123 @@ +{% extends "master.html" %} + +{% block title %}User {{username}}{% endblock %} +{% set tag = "users" %} +{% from "_browseheader.html" import browse_header %} + + +{% block header %} + +{% endblock %} + + +{% from "_render_repo.html" import render_repos_as_card, render_contributions_graph %} + + +{% block content %} +
+
+ {{browse_header(select=tag)}} +
+
+
+
+

+ {{ user.default_email | avatar(36) | safe }} {{ username }} + +

+

+ Joined {{ user.created | humanize }} +

+
+
+ {{ render_repos_as_card(repos, repos_length, 'Projects', 'repopage', repopage, total_page_repos) }} + {{ render_repos_as_card(forks, forks_length,'Forks', 'forkpage', forkpage, total_page_forks) }} + {{ render_contributions_graph(username) }} +
+
+
+
+ Groups {{ user.groups | length }} +
+ {% for group in user.groups %} + + {% else %} +
+

No group found

+
+ {% endfor %} +
+
+
+
+
+{% endblock %} + +{% block jscripts %} + {{ super() }} + + + +{% endblock %} diff --git a/pagure/templates/user_list.html b/pagure/templates/user_list.html new file mode 100644 index 0000000..b7092cb --- /dev/null +++ b/pagure/templates/user_list.html @@ -0,0 +1,159 @@ +{% extends "master.html" %} +{% block title %}Users{% endblock %} +{% set tag = "users" %} +{% from "_browseheader.html" import browse_header %} + +{% block header %} + +{% endblock %} + +{% block content %} +
+
+ {{browse_header(select=select)}} +
+
+ +
+ {% if total_page and total_page > 1 %} + + {% endif %} + +
+

+ Users {{ users_length }} + {%- if authenticated and config.get('PAGURE_AUTH') == 'local' %} + + + + {% endif -%} +

+ {% for user in users %} +
+
+ + +
{{ user.fullname }}
+
+
+ {{ user.user }} joined {{ + user.created | humanize }} +
+ +
+
+ {% endfor %} +
+ + {% if total_page and total_page > 1 %} + + {% endif %} +
+ +{% endblock %} + +{% block jscripts %} + {{ super() }} + + +{% endblock %} diff --git a/pagure/templates/user_requests.html b/pagure/templates/user_requests.html new file mode 100644 index 0000000..997ae37 --- /dev/null +++ b/pagure/templates/user_requests.html @@ -0,0 +1,109 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_field_in_row %} +{% from "_browseheader.html" import browse_header %} + +{% block title %}Pull-requests of {{ username }}{% endblock %} +{% set tag = "users"%} + + +{% block content %} +
+
+ {{browse_header(select=tag)}} +
+
+
+
+

+ Pull Requests opened by {{ username | avatar(20) | safe }} {{ username }} +

+
+ + Open + Merged + Closed + All + +
+ + + + + + + + + + + + + + + {% for request in requests %} + + + + + + + + + {% else %} + + + + {% endfor %} + +
#StatusTitleOpenedProjectAssigned to
PR#{{ request.id }}{{request.status}} + + {{ request.title | noJS("img") | safe }} + + + {{ + request.date_created | humanize}} + + + {{ request.project.username + '/' if request.project.is_fork }} + {{ request.project.name }} + + + {% if request.assignee %} + {{ request.assignee.default_email | avatar(16) | safe }} + {{ request.assignee.user }} + {% else %} + unassigned + {% endif %} +
No pull-request found
+
+
+ +{% endblock %} + +{% block jscripts %} + {{ super() }} + +{% endblock %} diff --git a/pagure/templates/user_settings.html b/pagure/templates/user_settings.html new file mode 100644 index 0000000..71c2a55 --- /dev/null +++ b/pagure/templates/user_settings.html @@ -0,0 +1,123 @@ +{% extends "master.html" %} +{% from "_formhelper.html" import render_bootstrap_field %} + +{% block title %}{{ user.user }}'s settings{% endblock %} +{% set tag = "users"%} + +{% macro render_email(email, form, validated=True) %} +
+  {{ email.email }} + {% if validated %} +
+ + {{ form.csrf_token }} + +
+ {% if email.email == user.default_email %} + + + + {% else %} +
+ + {{ form.csrf_token }} + + set as default + +
+ {% endif %} + {% else %} +
+ pending verification via email +
+ + {{ form.csrf_token }} + +
+
+ {% endif %} +
+{% endmacro %} + + +{% block content %} +
+
+
+
+ Basic Information +
+
+
+ +
{{ user.user }}
+
+
+ +
{{ user.fullname }}
+
+
+ + +
+
+
+ +
+
+ Email Addresses + + Add Email + +
+
+ {% for email in user.emails %} + {{ render_email(email, form) }} + {% endfor %} + {% for email in user.emails_pending %} + {{ render_email(email, form, validated=False) }} + {% endfor %} +
+
+ +
+
+ Authentication +
+
+
+ {{ render_bootstrap_field(form.ssh_key) }} +

+ + + {{ form.csrf_token }} +

+
+
+
+
+ {% if config.get('PAGURE_AUTH')=='local' %} + Change password + {% endif %} +
+
+
+ +{% endblock %} diff --git a/pagure/ui/__init__.py b/pagure/ui/__init__.py new file mode 100644 index 0000000..d296e8e --- /dev/null +++ b/pagure/ui/__init__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2014 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" diff --git a/pagure/ui/admin.py b/pagure/ui/admin.py new file mode 100644 index 0000000..776f299 --- /dev/null +++ b/pagure/ui/admin.py @@ -0,0 +1,96 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2014-2015 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +from functools import wraps + +import flask +from sqlalchemy.exc import SQLAlchemyError + +import pagure.exceptions +import pagure.forms +import pagure.lib +import pagure.lib.git +from pagure import (APP, SESSION, generate_user_key_files, + is_admin, admin_session_timedout) + +# pylint: disable=E1101 + + +def admin_required(function): + """ Flask decorator to retrict access to admins of pagure. + """ + @wraps(function) + def decorated_function(*args, **kwargs): + """ Decorated function, actually does the work. """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + elif not is_admin(): + flask.flash('Access restricted', 'error') + return flask.redirect(flask.url_for('.index')) + + return function(*args, **kwargs) + return decorated_function + + +# Application + + +@APP.route('/admin/') +@APP.route('/admin') +@admin_required +def admin_index(): + """ Front page of the admin section of the application. + """ + form = pagure.forms.ConfirmationForm() + + return flask.render_template( + 'admin_index.html', form=form, + ) + + +@APP.route('/admin/gitolite', methods=['POST']) +@admin_required +def admin_generate_acl(): + """ Regenerate the gitolite ACL file. """ + form = pagure.forms.ConfirmationForm() + if form.validate_on_submit(): + try: + pagure.lib.git.generate_gitolite_acls() + flask.flash('Gitolite ACLs updated') + except pagure.exceptions.PagureException as err: + flask.flash(str(err), 'error') + return flask.redirect(flask.url_for('admin_index')) + + +@APP.route('/admin/ssh', methods=['POST']) +@admin_required +def admin_refresh_ssh(): + """ Regenerate the user key files. """ + form = pagure.forms.ConfirmationForm() + if form.validate_on_submit(): + try: + generate_user_key_files() + pagure.lib.git.generate_gitolite_acls() + flask.flash('User key files regenerated') + except pagure.exceptions.PagureException as err: + flask.flash(str(err), 'error') + return flask.redirect(flask.url_for('admin_index')) + + +@APP.route('/admin/hook_token', methods=['POST']) +@admin_required +def admin_generate_hook_token(): + """ Regenerate the hook_token for each projects in the DB. """ + form = pagure.forms.ConfirmationForm() + if form.validate_on_submit(): + pagure.lib.generate_hook_token(SESSION) + flask.flash('Hook token all re-generated') + return flask.redirect(flask.url_for('admin_index')) diff --git a/pagure/ui/app.py b/pagure/ui/app.py new file mode 100644 index 0000000..b688dd6 --- /dev/null +++ b/pagure/ui/app.py @@ -0,0 +1,667 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2014-2015 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +import flask +from math import ceil + +from sqlalchemy.exc import SQLAlchemyError + +import pagure.exceptions +import pagure.lib +import pagure.lib.git +import pagure.forms +import pagure.ui.filters +from pagure import (APP, SESSION, login_required, + authenticated, + admin_session_timedout) + + +# Application +# pylint: disable=E1101 + + +@APP.route('/browse/projects', endpoint='browse_projects') +@APP.route('/browse/projects/', endpoint='browse_projects') +@APP.route('/') +def index(): + """ Front page of the application. + """ + sorting = flask.request.args.get('sorting') or None + page = flask.request.args.get('page', 1) + try: + page = int(page) + if page < 1: + page = 1 + except ValueError: + page = 1 + + limit = APP.config['ITEM_PER_PAGE'] + start = limit * (page - 1) + + repos = pagure.lib.search_projects( + SESSION, + fork=False, + start=start, + limit=limit, + sort=sorting) + + num_repos = pagure.lib.search_projects( + SESSION, + fork=False, + count=True) + total_page = int(ceil(num_repos / float(limit))) + + if authenticated() and flask.request.path == '/': + return index_auth() + + return flask.render_template( + 'index.html', + select="projects", + repos=repos, + repos_length=num_repos, + total_page=total_page, + page=page, + sorting=sorting, + ) + + +def index_auth(): + """ Front page for authenticated user. + """ + user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username) + if not user: + flask.abort(404, 'No user `%s` found, re-login maybe?' % ( + flask.g.fas_user.username)) + + repopage = flask.request.args.get('repopage', 1) + try: + repopage = int(repopage) + if repopage < 1: + page = 1 + except ValueError: + repopage = 1 + + forkpage = flask.request.args.get('forkpage', 1) + try: + forkpage = int(forkpage) + if forkpage < 1: + page = 1 + except ValueError: + forkpage = 1 + + repos = pagure.lib.search_projects( + SESSION, + username=flask.g.fas_user.username, + fork=False) + repos_length = pagure.lib.search_projects( + SESSION, + username=flask.g.fas_user.username, + fork=False, + count=True) + + forks = pagure.lib.search_projects( + SESSION, + username=flask.g.fas_user.username, + fork=True) + forks_length = pagure.lib.search_projects( + SESSION, + username=flask.g.fas_user.username, + fork=True, + count=True) + + return flask.render_template( + 'index_auth.html', + username=flask.g.fas_user.username, + user=user, + forks=forks, + repos=repos, + repopage=repopage, + forkpage=forkpage, + repos_length=repos_length, + forks_length=forks_length, + ) + + +@APP.route('/search/') +@APP.route('/search') +def search(): + """ Search this pagure instance for projects or users. + """ + stype = flask.request.args.get('type', 'projects') + term = flask.request.args.get('term') + page = flask.request.args.get('page', 1) + try: + page = int(page) + if page < 1: + page = 1 + except ValueError: + page = 1 + + if stype == 'projects': + return flask.redirect(flask.url_for('view_projects', pattern=term)) + elif stype == 'projects_forks': + return flask.redirect(flask.url_for( + 'view_projects', pattern=term, forks=True)) + else: + return flask.redirect(flask.url_for('view_users', username=term)) + + +@APP.route('/users/') +@APP.route('/users') +@APP.route('/users/') +def view_users(username=None): + """ Present the list of users. + """ + page = flask.request.args.get('page', 1) + try: + page = int(page) + if page < 1: + page = 1 + except ValueError: + page = 1 + + users = pagure.lib.search_user(SESSION, pattern=username) + + if len(users) == 1: + flask.flash('Only one result found, redirecting you to it') + return flask.redirect( + flask.url_for('view_user', username=users[0].username)) + + limit = APP.config['ITEM_PER_PAGE'] + start = limit * (page - 1) + end = limit * page + users_length = len(users) + users = users[start:end] + + total_page = int(ceil(users_length / float(limit))) + + for user in users: + repos_length = pagure.lib.search_projects( + SESSION, + username=user.user, + fork=False, + count=True) + + forks_length = pagure.lib.search_projects( + SESSION, + username=user.user, + fork=True, + count=True) + user.repos_length = repos_length + user.forks_length = forks_length + + return flask.render_template( + 'user_list.html', + users=users, + users_length=users_length, + total_page=total_page, + page=page, + select='users', + ) + + +@APP.route('/projects/') +@APP.route('/projects') +@APP.route('/projects/') +def view_projects(pattern=None): + """ Present the list of projects. + """ + forks = flask.request.args.get('forks') + page = flask.request.args.get('page', 1) + + try: + page = int(page) + if page < 1: + page = 1 + except ValueError: + page = 1 + + select = 'projects' + # If forks is specified, we want both forks and projects + if str(forks).lower() in ['true', '1']: + forks = None + select = 'projects_forks' + else: + forks = False + + limit = APP.config['ITEM_PER_PAGE'] + start = limit * (page - 1) + + projects = pagure.lib.search_projects( + SESSION, pattern=pattern, fork=forks, start=start, limit=limit) + + if len(projects) == 1: + flask.flash('Only one result found, redirecting you to it') + return flask.redirect(flask.url_for( + 'view_repo', repo=projects[0].name, + username=projects[0].user.username if projects[0].is_fork else None + )) + + limit = APP.config['ITEM_PER_PAGE'] + start = limit * (page - 1) + end = limit * page + projects_length = len(projects) + projects = projects[start:end] + + total_page = int(ceil(projects_length / float(limit))) + + return flask.render_template( + 'index.html', + repos=projects, + repos_length=projects_length, + total_page=total_page, + page=page, + select=select, + ) + + +@APP.route('/user//') +@APP.route('/user/') +def view_user(username): + """ Front page of a specific user. + """ + user = pagure.lib.search_user(SESSION, username=username) + if not user: + flask.abort(404, 'No user `%s` found' % username) + + repopage = flask.request.args.get('repopage', 1) + try: + repopage = int(repopage) + if repopage < 1: + repopage = 1 + except ValueError: + repopage = 1 + + forkpage = flask.request.args.get('forkpage', 1) + try: + forkpage = int(forkpage) + if forkpage < 1: + forkpage = 1 + except ValueError: + forkpage = 1 + + limit = APP.config['ITEM_PER_PAGE'] + repo_start = limit * (repopage - 1) + fork_start = limit * (forkpage - 1) + + repos = pagure.lib.search_projects( + SESSION, + username=username, + fork=False, + start=repo_start, + limit=limit) + repos_length = pagure.lib.search_projects( + SESSION, + username=username, + fork=False, + count=True) + + forks = pagure.lib.search_projects( + SESSION, + username=username, + fork=True, + start=fork_start, + limit=limit) + forks_length = pagure.lib.search_projects( + SESSION, + username=username, + fork=True, + count=True) + + total_page_repos = int(ceil(repos_length / float(limit))) + total_page_forks = int(ceil(forks_length / float(limit))) + + return flask.render_template( + 'user_info.html', + username=username, + user=user, + repos=repos, + total_page_repos=total_page_repos, + forks=forks, + total_page_forks=total_page_forks, + repopage=repopage, + forkpage=forkpage, + repos_length=repos_length, + forks_length=forks_length, + ) + + +@APP.route('/user//requests/') +@APP.route('/user//requests') +def view_user_requests(username): + """ Shows the pull-requests for the specified user. + """ + user = pagure.lib.search_user(SESSION, username=username) + if not user: + flask.abort(404, 'No user `%s` found' % username) + + requests = pagure.lib.get_pull_request_of_user( + SESSION, + username=username + ) + + return flask.render_template( + 'user_requests.html', + username=username, + user=user, + requests=requests, + ) + + +@APP.route('/new/', methods=('GET', 'POST')) +@APP.route('/new', methods=('GET', 'POST')) +@login_required +def new_project(): + """ Form to create a new project. + """ + user = pagure.lib.search_user(SESSION, username=flask.g.fas_user.username) + + if not pagure.APP.config.get('ENABLE_NEW_PROJECTS', True): + flask.abort(404, 'Creation of new project is not allowed on this \ + pagure instance') + + form = pagure.forms.ProjectForm() + if form.validate_on_submit(): + name = form.name.data + description = form.description.data + url = form.url.data + avatar_email = form.avatar_email.data + create_readme = form.create_readme.data + + try: + message = pagure.lib.new_project( + SESSION, + name=name, + description=description, + url=url, + avatar_email=avatar_email, + user=flask.g.fas_user.username, + blacklist=APP.config['BLACKLISTED_PROJECTS'], + allowed_prefix=APP.config['ALLOWED_PREFIX'], + gitfolder=APP.config['GIT_FOLDER'], + docfolder=APP.config['DOCS_FOLDER'], + ticketfolder=APP.config['TICKETS_FOLDER'], + requestfolder=APP.config['REQUESTS_FOLDER'], + add_readme=create_readme, + userobj=user, + ) + SESSION.commit() + pagure.lib.git.generate_gitolite_acls() + return flask.redirect(flask.url_for('view_repo', repo=name)) + except pagure.exceptions.PagureException as err: + flask.flash(str(err), 'error') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + flask.flash(str(err), 'error') + + return flask.render_template( + 'new_project.html', + form=form, + ) + + +@APP.route('/settings/', methods=('GET', 'POST')) +@APP.route('/settings', methods=('GET', 'POST')) +@login_required +def user_settings(): + """ Update the user settings. + """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + user = pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username) + if not user: + flask.abort(404, 'User not found') + + form = pagure.forms.UserSettingsForm() + if form.validate_on_submit(): + ssh_key = form.ssh_key.data + + try: + message = 'Nothing to update' + if user.public_ssh_key != ssh_key: + pagure.lib.update_user_ssh( + SESSION, + user=user, + ssh_key=ssh_key, + keydir=APP.config.get('GITOLITE_KEYDIR', None), + ) + SESSION.commit() + message = 'Public ssh key updated' + flask.flash(message) + return flask.redirect( + flask.url_for('.user_settings')) + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + flask.flash(str(err), 'error') + elif flask.request.method == 'GET': + form.ssh_key.data = user.public_ssh_key + + return flask.render_template( + 'user_settings.html', + user=user, + form=form, + ) + + +@APP.route('/markdown/', methods=['POST']) +def markdown_preview(): + """ Return the provided markdown text in html. + + The text has to be provided via the parameter 'content' of a POST query. + """ + form = pagure.forms.ConfirmationForm() + if form.validate_on_submit(): + return pagure.ui.filters.markdown_filter(flask.request.form['content']) + else: + flask.abort(400, 'Invalid request') + + +@APP.route('/settings/email/drop', methods=['POST']) +@login_required +def remove_user_email(): + """ Remove the specified email from the logged in user. + """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + user = pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username) + if not user: + flask.abort(404, 'User not found') + + if len(user.emails) == 1: + flask.flash( + 'You must always have at least one email', 'error') + return flask.redirect( + flask.url_for('.user_settings') + ) + + form = pagure.forms.UserEmailForm() + + if form.validate_on_submit(): + email = form.email.data + useremails = [mail.email for mail in user.emails] + + if email not in useremails: + flask.flash( + 'You do not have the email: %s, nothing to remove' % email, + 'error') + return flask.redirect( + flask.url_for('.user_settings') + ) + + for mail in user.emails: + if mail.email == email: + user.emails.remove(mail) + break + try: + SESSION.commit() + flask.flash('Email removed') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + APP.logger.exception(err) + flask.flash('Email could not be removed', 'error') + + return flask.redirect(flask.url_for('.user_settings')) + + +@APP.route('/settings/email/add/', methods=['GET', 'POST']) +@APP.route('/settings/email/add', methods=['GET', 'POST']) +@login_required +def add_user_email(): + """ Add a new email for the logged in user. + """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + user = pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username) + if not user: + flask.abort(404, 'User not found') + + form = pagure.forms.UserEmailForm( + emails=[mail.email for mail in user.emails]) + if form.validate_on_submit(): + email = form.email.data + + try: + pagure.lib.add_user_pending_email(SESSION, user, email) + SESSION.commit() + flask.flash('Email pending validation') + return flask.redirect(flask.url_for('.user_settings')) + except pagure.exceptions.PagureException as err: + flask.flash(str(err), 'error') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + APP.logger.exception(err) + flask.flash('Email could not be added', 'error') + + return flask.render_template( + 'user_emails.html', + user=user, + form=form, + ) + + +@APP.route('/settings/email/default', methods=['POST']) +@login_required +def set_default_email(): + """ Set the default email address of the user. + """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + user = pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username) + if not user: + flask.abort(404, 'User not found') + + form = pagure.forms.UserEmailForm() + if form.validate_on_submit(): + email = form.email.data + useremails = [mail.email for mail in user.emails] + + if email not in useremails: + flask.flash( + 'You do not have the email: %s, nothing to set' % email, + 'error') + + return flask.redirect( + flask.url_for('.user_settings') + ) + + user.default_email = email + + try: + SESSION.commit() + flask.flash('Default email set to: %s' % email) + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + APP.logger.exception(err) + flask.flash('Default email could not be set', 'error') + + return flask.redirect(flask.url_for('.user_settings')) + + +@APP.route('/settings/email/resend', methods=['POST']) +@login_required +def reconfirm_email(): + """ Re-send the email address of the user. + """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + user = pagure.lib.search_user( + SESSION, username=flask.g.fas_user.username) + if not user: + flask.abort(404, 'User not found') + + form = pagure.forms.UserEmailForm() + if form.validate_on_submit(): + email = form.email.data + + try: + pagure.lib.resend_pending_email(SESSION, user, email) + SESSION.commit() + flask.flash('Confirmation email re-sent') + except pagure.exceptions.PagureException as err: + flask.flash(str(err), 'error') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + APP.logger.exception(err) + flask.flash('Confirmation email could not be re-sent', 'error') + + return flask.redirect(flask.url_for('.user_settings')) + + +@APP.route('/settings/email/confirm//') +@APP.route('/settings/email/confirm/') +def confirm_email(token): + """ Confirm a new email. + """ + if admin_session_timedout(): + return flask.redirect( + flask.url_for('auth_login', next=flask.request.url)) + + email = pagure.lib.search_pending_email(SESSION, token=token) + if not email: + flask.flash('No email associated with this token.', 'error') + else: + try: + pagure.lib.add_email_to_user(SESSION, email.user, email.email) + SESSION.delete(email) + SESSION.commit() + flask.flash('Email validated') + except SQLAlchemyError as err: # pragma: no cover + SESSION.rollback() + flask.flash( + 'Could not set the account as active in the db, ' + 'please report this error to an admin', 'error') + APP.logger.exception(err) + + return flask.redirect(flask.url_for('.user_settings')) + + +@APP.route('/ssh_info/') +@APP.route('/ssh_info') +def ssh_hostkey(): + """ Endpoint returning information about the SSH hostkey and fingerprint + of the current pagure instance. + """ + return flask.render_template( + 'doc_ssh_keys.html', + ) diff --git a/pagure/ui/filters.py b/pagure/ui/filters.py new file mode 100644 index 0000000..a7a562d --- /dev/null +++ b/pagure/ui/filters.py @@ -0,0 +1,419 @@ +# -*- coding: utf-8 -*- + +""" + (c) 2014 - Copyright Red Hat Inc + + Authors: + Pierre-Yves Chibon + +""" + +import datetime +import textwrap + +import arrow +import flask +import md5 + +from pygments import highlight +from pygments.lexers.text import DiffLexer +from pygments.formatters import HtmlFormatter + +import pagure.exceptions +import pagure.lib +import pagure.forms +from pagure import (APP, SESSION, authenticated, is_repo_admin) + + +# Jinja filters + + +@APP.template_filter('hasattr') +def jinja_hasattr(obj, string): + """ Template filter checking if the provided object at the provided + string as attribute + """ + return hasattr(obj, string) + + +@APP.template_filter('humanize') +def humanize_date(date): + """ Template filter returning the last commit date of the provided repo. + """ + return arrow.get(date).humanize() + + +@APP.template_filter('format_ts') +def format_ts(string): + """ Template filter transforming a timestamp to a date + """ + dattime = datetime.datetime.fromtimestamp(int(string)) + return dattime.strftime('%b %d %Y %H:%M:%S') + + +@APP.template_filter('format_loc') +def format_loc(loc, commit=None, filename=None, tree_id=None, prequest=None, + index=None): + """ Template filter putting the provided lines of code into a table + """ + if loc is None: + return + + output = [ + '
', + '' + ] + + comments = {} + if prequest and not isinstance(prequest, flask.wrappers.Request): + for com in prequest.comments: + if commit and unicode(com.commit_id) == unicode(commit) \ + and unicode(com.filename) == unicode(filename): + if com.line in comments: + comments[com.line].append(com) + else: + comments[com.line] = [com] + for key in comments: + comments[key] = sorted( + comments[key], key=lambda obj: obj.date_created) + + if not index: + index = '' + + cnt = 1 + for line in loc.split('\n'): + if line == '': + break + if filename and commit: + output.append( + '' + '' % ( + { + 'cnt': '%s_%s' % (index, cnt), + 'cnt_lbl': cnt, + 'img': flask.url_for('static', filename='users.png'), + 'filename': filename.decode('UTF-8'), + 'commit': commit, + 'tree_id': tree_id, + } + ) + ) + else: + output.append( + '' + % ( + { + 'cnt': '%s_%s' % (index, cnt), + 'cnt_lbl': cnt, + } + ) + ) + + cnt += 1 + if not line: + output.append(line) + continue + if line.startswith('')[1] + output.append('' % line) + output.append('') + + tpl_edit = '' \ + '' \ + '' + tpl_edited = ' ' \ + 'Edited %(human_edit_date)s by %(user)s ' + + tpl_delete = '' + + if cnt - 1 in comments: + for comment in comments[cnt - 1]: + + templ_delete = '' + templ_edit = '' + templ_edited = '' + if authenticated() and ( + (str(comment.parent.status).lower() in ['true', 'open'] + and comment.user.user == flask.g.fas_user.username) + or is_repo_admin(comment.parent.project)): + templ_delete = tpl_delete % ({'commentid': comment.id}) + templ_edit = tpl_edit %({ + 'edit_url': flask.url_for( + 'pull_request_edit_comment', + repo=comment.parent.project.name, + requestid=comment.parent.id, + commentid=comment.id, + username=comment.parent.user.user \ + if comment.parent.project.is_fork else None + ), + 'requestid': comment.parent.id, + 'commentid': comment.id, + }) + + if comment.edited_on: + templ_edited = tpl_edited %({ + 'edit_date':comment.edited_on.strftime( + '%b %d %Y %H:%M:%S'), + 'human_edit_date': humanize_date(comment.edited_on), + 'user': comment.editor.user, + }) + + output.append( + '' + '' % ( + { + 'url': flask.url_for( + 'view_user', username=comment.user.user), + 'templ_delete': templ_delete, + 'templ_edit': templ_edit, + 'templ_edited': templ_edited, + 'user': comment.user.user, + 'avatar_url': avatar_url( + comment.user.default_email, 16), + 'date': comment.date_created.strftime( + '%b %d %Y %H:%M:%S'), + 'human_date': humanize_date(comment.date_created), + 'comment': markdown_filter(comment.comment), + 'commentid': comment.id, + 'anchor': u'¶', + } + ) + ) + + output.append('
' + '' + '

' + '' + '

' + '
' + '
%s
' + '
' + '
' + '
' + '' + ' %(user)s commented ' + '' + '%(human_date)s' + '
' + '
' + '
' + '%(comment)s' + '
' + '
' + '
' + '%(templ_edited)s' + '' + '
' + '
') + + return '\n'.join(output) + + +@APP.template_filter('wraps') +def text_wraps(text, size=10): + """ Template filter to wrap text at a specified size + """ + if text: + parts = textwrap.wrap(text, size) + if len(parts) > 1: + parts = '%s...' % parts[0] + else: + parts = parts[0] + return parts + + +@APP.template_filter('avatar') +def avatar(packager, size=64, default="retro"): + """ Template filter sorting the given branches, Fedora first then EPEL, + then whatever is left. + """ + if not '@' in packager: + user = pagure.lib.search_user(SESSION, username=packager) + if user: + packager = user.default_email + + output = '' % ( + avatar_url(packager, size) + ) + + return output + + +@APP.template_filter('avatar_url') +def avatar_url(email, size=64): + """ Template filter sorting the given branches, Fedora first then EPEL, + then whatever is left. + """ + return pagure.lib.avatar_url_from_openid(email, size) + + +@APP.template_filter('short') +def shorted_commit(cid): + """Gets short version of the commit id""" + return str(cid)[:APP.config['SHORT_LENGTH']] + + +@APP.template_filter('markdown') +def markdown_filter(text): + """ Template filter converting a string into html content using the + markdown library. + """ + return pagure.lib.text2markdown(text) + + +@APP.template_filter('html_diff') +def html_diff(diff): + """Display diff as HTML""" + if diff is None: + return + return highlight( + diff, + DiffLexer(), + HtmlFormatter( + noclasses=True, + style="tango",) + ) + + +@APP.template_filter('patch_to_diff') +def patch_to_diff(patch): + """Render a hunk as a diff""" + content = "" + for hunk in patch.hunks: + content = content + "@@ -%i,%i +%i,%i @@\n" % ( + hunk.old_start, hunk.old_lines, hunk.new_start, hunk.new_lines) + for line in hunk.lines: + if hasattr(line, 'content'): + origin = line.origin + if line.origin in ['<', '>', '=']: + origin = '' + content = content + origin + ' '+ line.content + else: + # Avoid situation where at the end of a file we get: + # + foo< + # \ No newline at end of file + if line[0] in ['<', '>', '=']: + line = ('', line[1]) + content = content + ' '.join(line) + + return content + + +@APP.template_filter('author2user') +def author_to_user(author, size=16): + """ Template filter transforming a pygit2 Author object into a text + either with just the username or linking to the user in pagure. + """ + output = author.name + if not author.email: + return output + user = pagure.lib.search_user(SESSION, email=author.email) + if user: + output = "%s %s" % ( + avatar(user.default_email, size), + flask.url_for('view_user', username=user.username), + author.name, + ) + return output + + +@APP.template_filter('author2avatar') +def author_to_avatar(author, size=32): + """ Template filter transforming a pygit2 Author object into an avatar. + """ + user = pagure.lib.search_user(SESSION, email=author.email) + output = user.default_email if user else author.email + return avatar(output.encode('utf-8'), size) + + +@APP.template_filter('InsertDiv') +def insert_div(content): + """ Template filter inserting an opening
and closing
+ after the first title and then at the end of the content. + """ + # This is quite a hack but simpler solution using .replace() didn't work + # for some reasons... + content = content.split('\n') + output = [] + for row in content: + if row.startswith('
' in row: + row = str(row).replace( + '

', + '

' + '  ' + ) + output.append(row) + output = "\n".join(output) + output = output.replace('

', '\n
', 1) + output = output.replace('h1', 'h3') + + return output + + +@APP.template_filter('noJS') +def no_js(content, ignore=None): + """ Template filter replacing