Squashed 'third_party/cgit/' content from commit 8fc0c81
git-subtree-dir: third_party/cgit git-subtree-split: 8fc0c81bbbed21ee30e8a48b2ab1066a029b7b32
This commit is contained in:
commit
723dc8fbcb
102 changed files with 15632 additions and 0 deletions
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Files I don't care to see in git-status/commit
|
||||
/cgit
|
||||
cgit.conf
|
||||
CGIT-CFLAGS
|
||||
VERSION
|
||||
cgitrc.5
|
||||
cgitrc.5.fo
|
||||
cgitrc.5.html
|
||||
cgitrc.5.pdf
|
||||
cgitrc.5.xml
|
||||
*.o
|
||||
*.d
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
[submodule "git"]
|
||||
url = https://git.kernel.org/pub/scm/git/git.git
|
||||
path = git
|
10
.mailmap
Normal file
10
.mailmap
Normal file
|
@ -0,0 +1,10 @@
|
|||
Florian Pritz <bluewind@xinu.at> <bluewind@xssn.at>
|
||||
Harley Laue <losinggeneration@gmail.com> <losinggeneration@aim.com>
|
||||
John Keeping <john@keeping.me.uk> <john@metanate.com>
|
||||
Lars Hjemli <hjemli@gmail.com> <larsh@hal-2004.(none)>
|
||||
Lars Hjemli <hjemli@gmail.com> <larsh@hatman.(none)>
|
||||
Lars Hjemli <hjemli@gmail.com> <larsh@slackbox.hjemli.net>
|
||||
Lars Hjemli <hjemli@gmail.com> <larsh@slaptop.hjemli.net>
|
||||
Lukas Fleischer <lfleischer@lfos.de> <cgit@cryptocrack.de>
|
||||
Lukas Fleischer <lfleischer@lfos.de> <info@cryptocrack.de>
|
||||
Stefan Bühler <source@stbuehler.de> <lighttpd@stbuehler.de>
|
13
AUTHORS
Normal file
13
AUTHORS
Normal file
|
@ -0,0 +1,13 @@
|
|||
Maintainer:
|
||||
Jason A. Donenfeld <Jason@zx2c4.com>
|
||||
|
||||
Contributors:
|
||||
Jason A. Donenfeld <Jason@zx2c4.com>
|
||||
Lukas Fleischer <cgit@cryptocrack.de>
|
||||
Johan Herland <johan@herland.net>
|
||||
Lars Hjemli <hjemli@gmail.com>
|
||||
Ferry Huberts <ferry.huberts@pelagic.nl>
|
||||
John Keeping <john@keeping.me.uk>
|
||||
|
||||
Previous Maintainer:
|
||||
Lars Hjemli <hjemli@gmail.com>
|
339
COPYING
Normal file
339
COPYING
Normal file
|
@ -0,0 +1,339 @@
|
|||
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 Lesser 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.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
<signature of Ty Coon>, 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 Lesser General
|
||||
Public License instead of this License.
|
170
Makefile
Normal file
170
Makefile
Normal file
|
@ -0,0 +1,170 @@
|
|||
all::
|
||||
|
||||
CGIT_VERSION = v1.2.1
|
||||
CGIT_SCRIPT_NAME = cgit.cgi
|
||||
CGIT_SCRIPT_PATH = /var/www/htdocs/cgit
|
||||
CGIT_DATA_PATH = $(CGIT_SCRIPT_PATH)
|
||||
CGIT_CONFIG = /etc/cgitrc
|
||||
CACHE_ROOT = /var/cache/cgit
|
||||
prefix = /usr/local
|
||||
libdir = $(prefix)/lib
|
||||
filterdir = $(libdir)/cgit/filters
|
||||
docdir = $(prefix)/share/doc/cgit
|
||||
htmldir = $(docdir)
|
||||
pdfdir = $(docdir)
|
||||
mandir = $(prefix)/share/man
|
||||
SHA1_HEADER = <openssl/sha.h>
|
||||
GIT_VER = 2.23.0
|
||||
GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.xz
|
||||
INSTALL = install
|
||||
COPYTREE = cp -r
|
||||
MAN5_TXT = $(wildcard *.5.txt)
|
||||
MAN_TXT = $(MAN5_TXT)
|
||||
DOC_MAN5 = $(patsubst %.txt,%,$(MAN5_TXT))
|
||||
DOC_HTML = $(patsubst %.txt,%.html,$(MAN_TXT))
|
||||
DOC_PDF = $(patsubst %.txt,%.pdf,$(MAN_TXT))
|
||||
|
||||
ASCIIDOC = asciidoc
|
||||
ASCIIDOC_EXTRA =
|
||||
ASCIIDOC_HTML = xhtml11
|
||||
ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA)
|
||||
TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML)
|
||||
|
||||
# Define NO_C99_FORMAT if your formatted IO functions (printf/scanf et.al.)
|
||||
# do not support the 'size specifiers' introduced by C99, namely ll, hh,
|
||||
# j, z, t. (representing long long int, char, intmax_t, size_t, ptrdiff_t).
|
||||
# some C compilers supported these specifiers prior to C99 as an extension.
|
||||
#
|
||||
# Define HAVE_LINUX_SENDFILE to use sendfile()
|
||||
|
||||
#-include config.mak
|
||||
|
||||
-include git/config.mak.uname
|
||||
#
|
||||
# Let the user override the above settings.
|
||||
#
|
||||
-include cgit.conf
|
||||
|
||||
export CGIT_VERSION CGIT_SCRIPT_NAME CGIT_SCRIPT_PATH CGIT_DATA_PATH CGIT_CONFIG CACHE_ROOT
|
||||
|
||||
#
|
||||
# Define a way to invoke make in subdirs quietly, shamelessly ripped
|
||||
# from git.git
|
||||
#
|
||||
QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
|
||||
QUIET_SUBDIR1 =
|
||||
|
||||
ifneq ($(findstring w,$(MAKEFLAGS)),w)
|
||||
PRINT_DIR = --no-print-directory
|
||||
else # "make -w"
|
||||
NO_SUBDIR = :
|
||||
endif
|
||||
|
||||
ifndef V
|
||||
QUIET_SUBDIR0 = +@subdir=
|
||||
QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \
|
||||
$(MAKE) $(PRINT_DIR) -C $$subdir
|
||||
QUIET_TAGS = @echo ' ' TAGS $@;
|
||||
export V
|
||||
endif
|
||||
|
||||
.SUFFIXES:
|
||||
|
||||
all:: cgit
|
||||
|
||||
cgit:
|
||||
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk ../cgit $(EXTRA_GIT_TARGETS) NO_CURL=1
|
||||
|
||||
sparse:
|
||||
$(QUIET_SUBDIR0)git $(QUIET_SUBDIR1) -f ../cgit.mk NO_CURL=1 cgit-sparse
|
||||
|
||||
test:
|
||||
@$(MAKE) --no-print-directory cgit EXTRA_GIT_TARGETS=all
|
||||
$(QUIET_SUBDIR0)tests $(QUIET_SUBDIR1) all
|
||||
|
||||
install: all
|
||||
$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_SCRIPT_PATH)
|
||||
$(INSTALL) -m 0755 cgit $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
|
||||
$(INSTALL) -m 0755 -d $(DESTDIR)$(CGIT_DATA_PATH)
|
||||
$(INSTALL) -m 0644 cgit.css $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
|
||||
$(INSTALL) -m 0644 cgit.png $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
|
||||
$(INSTALL) -m 0644 favicon.ico $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
|
||||
$(INSTALL) -m 0644 robots.txt $(DESTDIR)$(CGIT_DATA_PATH)/robots.txt
|
||||
$(INSTALL) -m 0755 -d $(DESTDIR)$(filterdir)
|
||||
$(COPYTREE) filters/* $(DESTDIR)$(filterdir)
|
||||
|
||||
install-doc: install-man install-html install-pdf
|
||||
|
||||
install-man: doc-man
|
||||
$(INSTALL) -m 0755 -d $(DESTDIR)$(mandir)/man5
|
||||
$(INSTALL) -m 0644 $(DOC_MAN5) $(DESTDIR)$(mandir)/man5
|
||||
|
||||
install-html: doc-html
|
||||
$(INSTALL) -m 0755 -d $(DESTDIR)$(htmldir)
|
||||
$(INSTALL) -m 0644 $(DOC_HTML) $(DESTDIR)$(htmldir)
|
||||
|
||||
install-pdf: doc-pdf
|
||||
$(INSTALL) -m 0755 -d $(DESTDIR)$(pdfdir)
|
||||
$(INSTALL) -m 0644 $(DOC_PDF) $(DESTDIR)$(pdfdir)
|
||||
|
||||
uninstall:
|
||||
rm -f $(DESTDIR)$(CGIT_SCRIPT_PATH)/$(CGIT_SCRIPT_NAME)
|
||||
rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.css
|
||||
rm -f $(DESTDIR)$(CGIT_DATA_PATH)/cgit.png
|
||||
rm -f $(DESTDIR)$(CGIT_DATA_PATH)/favicon.ico
|
||||
|
||||
uninstall-doc: uninstall-man uninstall-html uninstall-pdf
|
||||
|
||||
uninstall-man:
|
||||
@for i in $(DOC_MAN5); do \
|
||||
rm -fv $(DESTDIR)$(mandir)/man5/$$i; \
|
||||
done
|
||||
|
||||
uninstall-html:
|
||||
@for i in $(DOC_HTML); do \
|
||||
rm -fv $(DESTDIR)$(htmldir)/$$i; \
|
||||
done
|
||||
|
||||
uninstall-pdf:
|
||||
@for i in $(DOC_PDF); do \
|
||||
rm -fv $(DESTDIR)$(pdfdir)/$$i; \
|
||||
done
|
||||
|
||||
doc: doc-man doc-html doc-pdf
|
||||
doc-man: doc-man5
|
||||
doc-man5: $(DOC_MAN5)
|
||||
doc-html: $(DOC_HTML)
|
||||
doc-pdf: $(DOC_PDF)
|
||||
|
||||
%.5 : %.5.txt
|
||||
a2x -f manpage $<
|
||||
|
||||
$(DOC_HTML): %.html : %.txt
|
||||
$(TXT_TO_HTML) -o $@+ $< && \
|
||||
mv $@+ $@
|
||||
|
||||
$(DOC_PDF): %.pdf : %.txt
|
||||
a2x -f pdf cgitrc.5.txt
|
||||
|
||||
clean: clean-doc
|
||||
$(RM) cgit VERSION CGIT-CFLAGS *.o tags
|
||||
$(RM) -r .deps
|
||||
|
||||
cleanall: clean
|
||||
$(MAKE) -C git clean
|
||||
|
||||
clean-doc:
|
||||
$(RM) cgitrc.5 cgitrc.5.html cgitrc.5.pdf cgitrc.5.xml cgitrc.5.fo
|
||||
|
||||
get-git:
|
||||
curl -L $(GIT_URL) | tar -xJf - && rm -rf git && mv git-$(GIT_VER) git
|
||||
|
||||
tags:
|
||||
$(QUIET_TAGS)find . -name '*.[ch]' | xargs ctags
|
||||
|
||||
.PHONY: all cgit git get-git
|
||||
.PHONY: clean clean-doc cleanall
|
||||
.PHONY: doc doc-html doc-man doc-pdf
|
||||
.PHONY: install install-doc install-html install-man install-pdf
|
||||
.PHONY: tags test
|
||||
.PHONY: uninstall uninstall-doc uninstall-html uninstall-man uninstall-pdf
|
99
README
Normal file
99
README
Normal file
|
@ -0,0 +1,99 @@
|
|||
cgit - CGI for Git
|
||||
==================
|
||||
|
||||
This is an attempt to create a fast web interface for the Git SCM, using a
|
||||
built-in cache to decrease server I/O pressure.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Building cgit involves building a proper version of Git. How to do this
|
||||
depends on how you obtained the cgit sources:
|
||||
|
||||
a) If you're working in a cloned cgit repository, you first need to
|
||||
initialize and update the Git submodule:
|
||||
|
||||
$ git submodule init # register the Git submodule in .git/config
|
||||
$ $EDITOR .git/config # if you want to specify a different url for git
|
||||
$ git submodule update # clone/fetch and checkout correct git version
|
||||
|
||||
b) If you're building from a cgit tarball, you can download a proper git
|
||||
version like this:
|
||||
|
||||
$ make get-git
|
||||
|
||||
When either a) or b) has been performed, you can build and install cgit like
|
||||
this:
|
||||
|
||||
$ make
|
||||
$ sudo make install
|
||||
|
||||
This will install `cgit.cgi` and `cgit.css` into `/var/www/htdocs/cgit`. You
|
||||
can configure this location (and a few other things) by providing a `cgit.conf`
|
||||
file (see the Makefile for details).
|
||||
|
||||
If you'd like to compile without Lua support, you may use:
|
||||
|
||||
$ make NO_LUA=1
|
||||
|
||||
And if you'd like to specify a Lua implementation, you may use:
|
||||
|
||||
$ make LUA_PKGCONFIG=lua5.1
|
||||
|
||||
If this is not specified, the Lua implementation will be auto-detected,
|
||||
preferring LuaJIT if many are present. Acceptable values are generally "lua",
|
||||
"luajit", "lua5.1", and "lua5.2".
|
||||
|
||||
|
||||
Dependencies
|
||||
------------
|
||||
|
||||
* libzip
|
||||
* libcrypto (OpenSSL)
|
||||
* libssl (OpenSSL)
|
||||
* optional: luajit or lua, most reliably used when pkg-config is available
|
||||
|
||||
Apache configuration
|
||||
--------------------
|
||||
|
||||
A new `Directory` section must probably be added for cgit, possibly something
|
||||
like this:
|
||||
|
||||
<Directory "/var/www/htdocs/cgit/">
|
||||
AllowOverride None
|
||||
Options +ExecCGI
|
||||
Order allow,deny
|
||||
Allow from all
|
||||
</Directory>
|
||||
|
||||
|
||||
Runtime configuration
|
||||
---------------------
|
||||
|
||||
The file `/etc/cgitrc` is read by cgit before handling a request. In addition
|
||||
to runtime parameters, this file may also contain a list of repositories
|
||||
displayed by cgit (see `cgitrc.5.txt` for further details).
|
||||
|
||||
The cache
|
||||
---------
|
||||
|
||||
When cgit is invoked it looks for a cache file matching the request and
|
||||
returns it to the client. If no such cache file exists (or if it has expired),
|
||||
the content for the request is written into the proper cache file before the
|
||||
file is returned.
|
||||
|
||||
If the cache file has expired but cgit is unable to obtain a lock for it, the
|
||||
stale cache file is returned to the client. This is done to favour page
|
||||
throughput over page freshness.
|
||||
|
||||
The generated content contains the complete response to the client, including
|
||||
the HTTP headers `Modified` and `Expires`.
|
||||
|
||||
Online presence
|
||||
---------------
|
||||
|
||||
* The cgit homepage is hosted by cgit at <https://git.zx2c4.com/cgit/about/>
|
||||
|
||||
* Patches, bug reports, discussions and support should go to the cgit
|
||||
mailing list: <cgit@lists.zx2c4.com>. To sign up, visit
|
||||
<https://lists.zx2c4.com/mailman/listinfo/cgit>
|
468
cache.c
Normal file
468
cache.c
Normal file
|
@ -0,0 +1,468 @@
|
|||
/* cache.c: cache management
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*
|
||||
*
|
||||
* The cache is just a directory structure where each file is a cache slot,
|
||||
* and each filename is based on the hash of some key (e.g. the cgit url).
|
||||
* Each file contains the full key followed by the cached content for that
|
||||
* key.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "cache.h"
|
||||
#include "html.h"
|
||||
#ifdef HAVE_LINUX_SENDFILE
|
||||
#include <sys/sendfile.h>
|
||||
#endif
|
||||
|
||||
#define CACHE_BUFSIZE (1024 * 4)
|
||||
|
||||
struct cache_slot {
|
||||
const char *key;
|
||||
size_t keylen;
|
||||
int ttl;
|
||||
cache_fill_fn fn;
|
||||
int cache_fd;
|
||||
int lock_fd;
|
||||
int stdout_fd;
|
||||
const char *cache_name;
|
||||
const char *lock_name;
|
||||
int match;
|
||||
struct stat cache_st;
|
||||
int bufsize;
|
||||
char buf[CACHE_BUFSIZE];
|
||||
};
|
||||
|
||||
/* Open an existing cache slot and fill the cache buffer with
|
||||
* (part of) the content of the cache file. Return 0 on success
|
||||
* and errno otherwise.
|
||||
*/
|
||||
static int open_slot(struct cache_slot *slot)
|
||||
{
|
||||
char *bufz;
|
||||
ssize_t bufkeylen = -1;
|
||||
|
||||
slot->cache_fd = open(slot->cache_name, O_RDONLY);
|
||||
if (slot->cache_fd == -1)
|
||||
return errno;
|
||||
|
||||
if (fstat(slot->cache_fd, &slot->cache_st))
|
||||
return errno;
|
||||
|
||||
slot->bufsize = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
|
||||
if (slot->bufsize < 0)
|
||||
return errno;
|
||||
|
||||
bufz = memchr(slot->buf, 0, slot->bufsize);
|
||||
if (bufz)
|
||||
bufkeylen = bufz - slot->buf;
|
||||
|
||||
if (slot->key)
|
||||
slot->match = bufkeylen == slot->keylen &&
|
||||
!memcmp(slot->key, slot->buf, bufkeylen + 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Close the active cache slot */
|
||||
static int close_slot(struct cache_slot *slot)
|
||||
{
|
||||
int err = 0;
|
||||
if (slot->cache_fd > 0) {
|
||||
if (close(slot->cache_fd))
|
||||
err = errno;
|
||||
else
|
||||
slot->cache_fd = -1;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Print the content of the active cache slot (but skip the key). */
|
||||
static int print_slot(struct cache_slot *slot)
|
||||
{
|
||||
#ifdef HAVE_LINUX_SENDFILE
|
||||
off_t start_off;
|
||||
int ret;
|
||||
|
||||
start_off = slot->keylen + 1;
|
||||
|
||||
do {
|
||||
ret = sendfile(STDOUT_FILENO, slot->cache_fd, &start_off,
|
||||
slot->cache_st.st_size - start_off);
|
||||
if (ret < 0) {
|
||||
if (errno == EAGAIN || errno == EINTR)
|
||||
continue;
|
||||
return errno;
|
||||
}
|
||||
return 0;
|
||||
} while (1);
|
||||
#else
|
||||
ssize_t i, j;
|
||||
|
||||
i = lseek(slot->cache_fd, slot->keylen + 1, SEEK_SET);
|
||||
if (i != slot->keylen + 1)
|
||||
return errno;
|
||||
|
||||
do {
|
||||
i = j = xread(slot->cache_fd, slot->buf, sizeof(slot->buf));
|
||||
if (i > 0)
|
||||
j = xwrite(STDOUT_FILENO, slot->buf, i);
|
||||
} while (i > 0 && j == i);
|
||||
|
||||
if (i < 0 || j != i)
|
||||
return errno;
|
||||
else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
/* Check if the slot has expired */
|
||||
static int is_expired(struct cache_slot *slot)
|
||||
{
|
||||
if (slot->ttl < 0)
|
||||
return 0;
|
||||
else
|
||||
return slot->cache_st.st_mtime + slot->ttl * 60 < time(NULL);
|
||||
}
|
||||
|
||||
/* Check if the slot has been modified since we opened it.
|
||||
* NB: If stat() fails, we pretend the file is modified.
|
||||
*/
|
||||
static int is_modified(struct cache_slot *slot)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(slot->cache_name, &st))
|
||||
return 1;
|
||||
return (st.st_ino != slot->cache_st.st_ino ||
|
||||
st.st_mtime != slot->cache_st.st_mtime ||
|
||||
st.st_size != slot->cache_st.st_size);
|
||||
}
|
||||
|
||||
/* Close an open lockfile */
|
||||
static int close_lock(struct cache_slot *slot)
|
||||
{
|
||||
int err = 0;
|
||||
if (slot->lock_fd > 0) {
|
||||
if (close(slot->lock_fd))
|
||||
err = errno;
|
||||
else
|
||||
slot->lock_fd = -1;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Create a lockfile used to store the generated content for a cache
|
||||
* slot, and write the slot key + \0 into it.
|
||||
* Returns 0 on success and errno otherwise.
|
||||
*/
|
||||
static int lock_slot(struct cache_slot *slot)
|
||||
{
|
||||
struct flock lock = {
|
||||
.l_type = F_WRLCK,
|
||||
.l_whence = SEEK_SET,
|
||||
.l_start = 0,
|
||||
.l_len = 0,
|
||||
};
|
||||
|
||||
slot->lock_fd = open(slot->lock_name, O_RDWR | O_CREAT,
|
||||
S_IRUSR | S_IWUSR);
|
||||
if (slot->lock_fd == -1)
|
||||
return errno;
|
||||
if (fcntl(slot->lock_fd, F_SETLK, &lock) < 0) {
|
||||
int saved_errno = errno;
|
||||
close(slot->lock_fd);
|
||||
slot->lock_fd = -1;
|
||||
return saved_errno;
|
||||
}
|
||||
if (xwrite(slot->lock_fd, slot->key, slot->keylen + 1) < 0)
|
||||
return errno;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Release the current lockfile. If `replace_old_slot` is set the
|
||||
* lockfile replaces the old cache slot, otherwise the lockfile is
|
||||
* just deleted.
|
||||
*/
|
||||
static int unlock_slot(struct cache_slot *slot, int replace_old_slot)
|
||||
{
|
||||
int err;
|
||||
|
||||
if (replace_old_slot)
|
||||
err = rename(slot->lock_name, slot->cache_name);
|
||||
else
|
||||
err = unlink(slot->lock_name);
|
||||
|
||||
/* Restore stdout and close the temporary FD. */
|
||||
if (slot->stdout_fd >= 0) {
|
||||
dup2(slot->stdout_fd, STDOUT_FILENO);
|
||||
close(slot->stdout_fd);
|
||||
slot->stdout_fd = -1;
|
||||
}
|
||||
|
||||
if (err)
|
||||
return errno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Generate the content for the current cache slot by redirecting
|
||||
* stdout to the lock-fd and invoking the callback function
|
||||
*/
|
||||
static int fill_slot(struct cache_slot *slot)
|
||||
{
|
||||
/* Preserve stdout */
|
||||
slot->stdout_fd = dup(STDOUT_FILENO);
|
||||
if (slot->stdout_fd == -1)
|
||||
return errno;
|
||||
|
||||
/* Redirect stdout to lockfile */
|
||||
if (dup2(slot->lock_fd, STDOUT_FILENO) == -1)
|
||||
return errno;
|
||||
|
||||
/* Generate cache content */
|
||||
slot->fn();
|
||||
|
||||
/* Make sure any buffered data is flushed to the file */
|
||||
if (fflush(stdout))
|
||||
return errno;
|
||||
|
||||
/* update stat info */
|
||||
if (fstat(slot->lock_fd, &slot->cache_st))
|
||||
return errno;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Crude implementation of 32-bit FNV-1 hash algorithm,
|
||||
* see http://www.isthe.com/chongo/tech/comp/fnv/ for details
|
||||
* about the magic numbers.
|
||||
*/
|
||||
#define FNV_OFFSET 0x811c9dc5
|
||||
#define FNV_PRIME 0x01000193
|
||||
|
||||
unsigned long hash_str(const char *str)
|
||||
{
|
||||
unsigned long h = FNV_OFFSET;
|
||||
unsigned char *s = (unsigned char *)str;
|
||||
|
||||
if (!s)
|
||||
return h;
|
||||
|
||||
while (*s) {
|
||||
h *= FNV_PRIME;
|
||||
h ^= *s++;
|
||||
}
|
||||
return h;
|
||||
}
|
||||
|
||||
static int process_slot(struct cache_slot *slot)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = open_slot(slot);
|
||||
if (!err && slot->match) {
|
||||
if (is_expired(slot)) {
|
||||
if (!lock_slot(slot)) {
|
||||
/* If the cachefile has been replaced between
|
||||
* `open_slot` and `lock_slot`, we'll just
|
||||
* serve the stale content from the original
|
||||
* cachefile. This way we avoid pruning the
|
||||
* newly generated slot. The same code-path
|
||||
* is chosen if fill_slot() fails for some
|
||||
* reason.
|
||||
*
|
||||
* TODO? check if the new slot contains the
|
||||
* same key as the old one, since we would
|
||||
* prefer to serve the newest content.
|
||||
* This will require us to open yet another
|
||||
* file-descriptor and read and compare the
|
||||
* key from the new file, so for now we're
|
||||
* lazy and just ignore the new file.
|
||||
*/
|
||||
if (is_modified(slot) || fill_slot(slot)) {
|
||||
unlock_slot(slot, 0);
|
||||
close_lock(slot);
|
||||
} else {
|
||||
close_slot(slot);
|
||||
unlock_slot(slot, 1);
|
||||
slot->cache_fd = slot->lock_fd;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((err = print_slot(slot)) != 0) {
|
||||
cache_log("[cgit] error printing cache %s: %s (%d)\n",
|
||||
slot->cache_name,
|
||||
strerror(err),
|
||||
err);
|
||||
}
|
||||
close_slot(slot);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* If the cache slot does not exist (or its key doesn't match the
|
||||
* current key), lets try to create a new cache slot for this
|
||||
* request. If this fails (for whatever reason), lets just generate
|
||||
* the content without caching it and fool the caller to believe
|
||||
* everything worked out (but print a warning on stdout).
|
||||
*/
|
||||
|
||||
close_slot(slot);
|
||||
if ((err = lock_slot(slot)) != 0) {
|
||||
cache_log("[cgit] Unable to lock slot %s: %s (%d)\n",
|
||||
slot->lock_name, strerror(err), err);
|
||||
slot->fn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ((err = fill_slot(slot)) != 0) {
|
||||
cache_log("[cgit] Unable to fill slot %s: %s (%d)\n",
|
||||
slot->lock_name, strerror(err), err);
|
||||
unlock_slot(slot, 0);
|
||||
close_lock(slot);
|
||||
slot->fn();
|
||||
return 0;
|
||||
}
|
||||
// We've got a valid cache slot in the lock file, which
|
||||
// is about to replace the old cache slot. But if we
|
||||
// release the lockfile and then try to open the new cache
|
||||
// slot, we might get a race condition with a concurrent
|
||||
// writer for the same cache slot (with a different key).
|
||||
// Lets avoid such a race by just printing the content of
|
||||
// the lock file.
|
||||
slot->cache_fd = slot->lock_fd;
|
||||
unlock_slot(slot, 1);
|
||||
if ((err = print_slot(slot)) != 0) {
|
||||
cache_log("[cgit] error printing cache %s: %s (%d)\n",
|
||||
slot->cache_name,
|
||||
strerror(err),
|
||||
err);
|
||||
}
|
||||
close_slot(slot);
|
||||
return err;
|
||||
}
|
||||
|
||||
/* Print cached content to stdout, generate the content if necessary. */
|
||||
int cache_process(int size, const char *path, const char *key, int ttl,
|
||||
cache_fill_fn fn)
|
||||
{
|
||||
unsigned long hash;
|
||||
int i;
|
||||
struct strbuf filename = STRBUF_INIT;
|
||||
struct strbuf lockname = STRBUF_INIT;
|
||||
struct cache_slot slot;
|
||||
int result;
|
||||
|
||||
/* If the cache is disabled, just generate the content */
|
||||
if (size <= 0 || ttl == 0) {
|
||||
fn();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Verify input, calculate filenames */
|
||||
if (!path) {
|
||||
cache_log("[cgit] Cache path not specified, caching is disabled\n");
|
||||
fn();
|
||||
return 0;
|
||||
}
|
||||
if (!key)
|
||||
key = "";
|
||||
hash = hash_str(key) % size;
|
||||
strbuf_addstr(&filename, path);
|
||||
strbuf_ensure_end(&filename, '/');
|
||||
for (i = 0; i < 8; i++) {
|
||||
strbuf_addf(&filename, "%x", (unsigned char)(hash & 0xf));
|
||||
hash >>= 4;
|
||||
}
|
||||
strbuf_addbuf(&lockname, &filename);
|
||||
strbuf_addstr(&lockname, ".lock");
|
||||
slot.fn = fn;
|
||||
slot.ttl = ttl;
|
||||
slot.stdout_fd = -1;
|
||||
slot.cache_name = filename.buf;
|
||||
slot.lock_name = lockname.buf;
|
||||
slot.key = key;
|
||||
slot.keylen = strlen(key);
|
||||
result = process_slot(&slot);
|
||||
|
||||
strbuf_release(&filename);
|
||||
strbuf_release(&lockname);
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Return a strftime formatted date/time
|
||||
* NB: the result from this function is to shared memory
|
||||
*/
|
||||
static char *sprintftime(const char *format, time_t time)
|
||||
{
|
||||
static char buf[64];
|
||||
struct tm *tm;
|
||||
|
||||
if (!time)
|
||||
return NULL;
|
||||
tm = gmtime(&time);
|
||||
strftime(buf, sizeof(buf)-1, format, tm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int cache_ls(const char *path)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
int err = 0;
|
||||
struct cache_slot slot = { NULL };
|
||||
struct strbuf fullname = STRBUF_INIT;
|
||||
size_t prefixlen;
|
||||
|
||||
if (!path) {
|
||||
cache_log("[cgit] cache path not specified\n");
|
||||
return -1;
|
||||
}
|
||||
dir = opendir(path);
|
||||
if (!dir) {
|
||||
err = errno;
|
||||
cache_log("[cgit] unable to open path %s: %s (%d)\n",
|
||||
path, strerror(err), err);
|
||||
return err;
|
||||
}
|
||||
strbuf_addstr(&fullname, path);
|
||||
strbuf_ensure_end(&fullname, '/');
|
||||
prefixlen = fullname.len;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (strlen(ent->d_name) != 8)
|
||||
continue;
|
||||
strbuf_setlen(&fullname, prefixlen);
|
||||
strbuf_addstr(&fullname, ent->d_name);
|
||||
slot.cache_name = fullname.buf;
|
||||
if ((err = open_slot(&slot)) != 0) {
|
||||
cache_log("[cgit] unable to open path %s: %s (%d)\n",
|
||||
fullname.buf, strerror(err), err);
|
||||
continue;
|
||||
}
|
||||
htmlf("%s %s %10"PRIuMAX" %s\n",
|
||||
fullname.buf,
|
||||
sprintftime("%Y-%m-%d %H:%M:%S",
|
||||
slot.cache_st.st_mtime),
|
||||
(uintmax_t)slot.cache_st.st_size,
|
||||
slot.buf);
|
||||
close_slot(&slot);
|
||||
}
|
||||
closedir(dir);
|
||||
strbuf_release(&fullname);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Print a message to stdout */
|
||||
void cache_log(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vfprintf(stderr, format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
37
cache.h
Normal file
37
cache.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Since git has it's own cache.h which we include,
|
||||
* lets test on CGIT_CACHE_H to avoid confusion
|
||||
*/
|
||||
|
||||
#ifndef CGIT_CACHE_H
|
||||
#define CGIT_CACHE_H
|
||||
|
||||
typedef void (*cache_fill_fn)(void);
|
||||
|
||||
|
||||
/* Print cached content to stdout, generate the content if necessary.
|
||||
*
|
||||
* Parameters
|
||||
* size max number of cache files
|
||||
* path directory used to store cache files
|
||||
* key the key used to lookup cache files
|
||||
* ttl max cache time in seconds for this key
|
||||
* fn content generator function for this key
|
||||
*
|
||||
* Return value
|
||||
* 0 indicates success, everything else is an error
|
||||
*/
|
||||
extern int cache_process(int size, const char *path, const char *key, int ttl,
|
||||
cache_fill_fn fn);
|
||||
|
||||
|
||||
/* List info about all cache entries on stdout */
|
||||
extern int cache_ls(const char *path);
|
||||
|
||||
/* Print a message to stdout */
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern void cache_log(const char *format, ...);
|
||||
|
||||
extern unsigned long hash_str(const char *str);
|
||||
|
||||
#endif /* CGIT_CACHE_H */
|
895
cgit.css
Normal file
895
cgit.css
Normal file
|
@ -0,0 +1,895 @@
|
|||
div#cgit {
|
||||
padding: 0em;
|
||||
margin: 0em;
|
||||
font-family: sans-serif;
|
||||
font-size: 10pt;
|
||||
color: #333;
|
||||
background: white;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
div#cgit a {
|
||||
color: blue;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#cgit a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div#cgit table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
div#cgit table#header {
|
||||
width: 100%;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div#cgit table#header td.logo {
|
||||
width: 96px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div#cgit table#header td.main {
|
||||
font-size: 250%;
|
||||
padding-left: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div#cgit table#header td.main a {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
div#cgit table#header td.form {
|
||||
text-align: right;
|
||||
vertical-align: bottom;
|
||||
padding-right: 1em;
|
||||
padding-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div#cgit table#header td.form form,
|
||||
div#cgit table#header td.form input,
|
||||
div#cgit table#header td.form select {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div#cgit table#header td.sub {
|
||||
color: #777;
|
||||
border-top: solid 1px #ccc;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
div#cgit table.tabs {
|
||||
border-bottom: solid 3px #ccc;
|
||||
border-collapse: collapse;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 0px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#cgit table.tabs td {
|
||||
padding: 0px 1em;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
div#cgit table.tabs td a {
|
||||
padding: 2px 0.75em;
|
||||
color: #777;
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
div#cgit table.tabs td a.active {
|
||||
color: #000;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
div#cgit table.tabs a[href^="http://"]:after, div#cgit table.tabs a[href^="https://"]:after {
|
||||
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAAmJLR0QA/4ePzL8AAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQfgAhcJDQY+gm2TAAAAHWlUWHRDb21tZW50AAAAAABDcmVhdGVkIHdpdGggR0lNUGQuZQcAAABbSURBVAhbY2BABs4MU4CwhYHBh2Erww4wrGFQZHjI8B8IgUIscJWyDHcggltQhI4zGDCcRwhChPggHIggP1QoAVmQkSETrGoHsiAEsACtBYN0oDAMbgU6EBcAAL2eHUt4XUU4AAAAAElFTkSuQmCC);
|
||||
opacity: 0.5;
|
||||
margin: 0 0 0 5px;
|
||||
}
|
||||
|
||||
div#cgit table.tabs td.form {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div#cgit table.tabs td.form form {
|
||||
padding-bottom: 2px;
|
||||
font-size: 90%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div#cgit table.tabs td.form input,
|
||||
div#cgit table.tabs td.form select {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div#cgit div.path {
|
||||
margin: 0px;
|
||||
padding: 5px 2em 2px 2em;
|
||||
color: #000;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div#cgit div.content {
|
||||
margin: 0px;
|
||||
padding: 2em;
|
||||
border-bottom: solid 3px #ccc;
|
||||
}
|
||||
|
||||
|
||||
div#cgit table.list {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
div#cgit table.list tr {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div#cgit table.list tr.logheader {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
div#cgit table.list tr:nth-child(even) {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
div#cgit table.list tr:nth-child(odd) {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div#cgit table.list tr:hover {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
div#cgit table.list tr.nohover {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div#cgit table.list tr.nohover:hover {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div#cgit table.list tr.nohover-highlight:hover:nth-child(even) {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
div#cgit table.list tr.nohover-highlight:hover:nth-child(odd) {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div#cgit table.list th {
|
||||
font-weight: bold;
|
||||
/* color: #888;
|
||||
border-top: dashed 1px #888;
|
||||
border-bottom: dashed 1px #888;
|
||||
*/
|
||||
padding: 0.1em 0.5em 0.05em 0.5em;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
div#cgit table.list td {
|
||||
border: none;
|
||||
padding: 0.1em 0.5em 0.1em 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph .column1 {
|
||||
color: #a00;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph .column2 {
|
||||
color: #0a0;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph .column3 {
|
||||
color: #aa0;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph .column4 {
|
||||
color: #00a;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph .column5 {
|
||||
color: #a0a;
|
||||
}
|
||||
|
||||
div#cgit table.list td.commitgraph .column6 {
|
||||
color: #0aa;
|
||||
}
|
||||
|
||||
div#cgit table.list td.logsubject {
|
||||
font-family: monospace;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div#cgit table.list td.logmsg {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
padding: 0 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.list td a {
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#cgit table.list td a.ls-dir {
|
||||
font-weight: bold;
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
div#cgit table.list td a:hover {
|
||||
color: #00f;
|
||||
}
|
||||
|
||||
div#cgit img {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#cgit input#switch-btn {
|
||||
margin: 2px 0px 0px 0px;
|
||||
}
|
||||
|
||||
div#cgit td#sidebar input.txt {
|
||||
width: 100%;
|
||||
margin: 2px 0px 0px 0px;
|
||||
}
|
||||
|
||||
div#cgit table#grid {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
div#cgit td#content {
|
||||
vertical-align: top;
|
||||
padding: 1em 2em 1em 1em;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#cgit div#summary {
|
||||
vertical-align: top;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
div#cgit table#downloads {
|
||||
float: right;
|
||||
border-collapse: collapse;
|
||||
border: solid 1px #777;
|
||||
margin-left: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table#downloads th {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
div#cgit div#blob {
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
div#cgit div.error {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
margin: 1em 2em;
|
||||
}
|
||||
|
||||
div#cgit a.ls-blob, div#cgit a.ls-dir, div#cgit .ls-mod {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
div#cgit td.ls-size {
|
||||
text-align: right;
|
||||
font-family: monospace;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
div#cgit td.ls-mode {
|
||||
font-family: monospace;
|
||||
width: 10em;
|
||||
}
|
||||
|
||||
div#cgit table.blob {
|
||||
margin-top: 0.5em;
|
||||
border-top: solid 1px black;
|
||||
}
|
||||
|
||||
div#cgit table.blob td.hashes,
|
||||
div#cgit table.blob td.lines {
|
||||
margin: 0; padding: 0 0 0 0.5em;
|
||||
vertical-align: top;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#cgit table.blob td.linenumbers {
|
||||
margin: 0; padding: 0 0.5em 0 0.5em;
|
||||
vertical-align: top;
|
||||
text-align: right;
|
||||
border-right: 1px solid gray;
|
||||
}
|
||||
|
||||
div#cgit table.blob pre {
|
||||
padding: 0; margin: 0;
|
||||
}
|
||||
|
||||
div#cgit table.blob td.linenumbers a,
|
||||
div#cgit table.ssdiff td.lineno a {
|
||||
color: gray;
|
||||
text-align: right;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#cgit table.blob td.linenumbers a:hover,
|
||||
div#cgit table.ssdiff td.lineno a:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#cgit table.blame td.hashes,
|
||||
div#cgit table.blame td.lines,
|
||||
div#cgit table.blame td.linenumbers {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#cgit table.blame td.hashes div.alt,
|
||||
div#cgit table.blame td.lines div.alt {
|
||||
padding: 0 0.5em 0 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.blame td.linenumbers div.alt {
|
||||
padding: 0 0.5em 0 0;
|
||||
}
|
||||
|
||||
div#cgit table.blame div.alt:nth-child(even) {
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
div#cgit table.blame div.alt:nth-child(odd) {
|
||||
background: white;
|
||||
}
|
||||
|
||||
div#cgit table.blame td.lines > div {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div#cgit table.blame td.lines > div > pre {
|
||||
padding: 0 0 0 0.5em;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
div#cgit table.bin-blob {
|
||||
margin-top: 0.5em;
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
div#cgit table.bin-blob th {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
border: solid 1px #777;
|
||||
padding: 0.5em 1em;
|
||||
}
|
||||
|
||||
div#cgit table.bin-blob td {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
border-left: solid 1px #777;
|
||||
padding: 0em 1em;
|
||||
}
|
||||
|
||||
div#cgit table.nowrap td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div#cgit table.commit-info {
|
||||
border-collapse: collapse;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
div#cgit div.cgit-panel {
|
||||
float: right;
|
||||
margin-top: 1.5em;
|
||||
}
|
||||
|
||||
div#cgit div.cgit-panel table {
|
||||
border-collapse: collapse;
|
||||
border: solid 1px #aaa;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div#cgit div.cgit-panel th {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div#cgit div.cgit-panel td {
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
div#cgit div.cgit-panel td.label {
|
||||
padding-right: 0.5em;
|
||||
}
|
||||
|
||||
div#cgit div.cgit-panel td.ctrl {
|
||||
padding-left: 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.commit-info th {
|
||||
text-align: left;
|
||||
font-weight: normal;
|
||||
padding: 0.1em 1em 0.1em 0.1em;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
div#cgit table.commit-info td {
|
||||
font-weight: normal;
|
||||
padding: 0.1em 1em 0.1em 0.1em;
|
||||
}
|
||||
|
||||
div#cgit div.commit-subject {
|
||||
font-weight: bold;
|
||||
font-size: 125%;
|
||||
margin: 1.5em 0em 0.5em 0em;
|
||||
padding: 0em;
|
||||
}
|
||||
|
||||
div#cgit div.commit-msg {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
div#cgit div.notes-header {
|
||||
font-weight: bold;
|
||||
padding-top: 1.5em;
|
||||
}
|
||||
|
||||
div#cgit div.notes {
|
||||
white-space: pre;
|
||||
font-family: monospace;
|
||||
border: solid 1px #ee9;
|
||||
background-color: #ffd;
|
||||
padding: 0.3em 2em 0.3em 1em;
|
||||
float: left;
|
||||
}
|
||||
|
||||
div#cgit div.notes-footer {
|
||||
clear: left;
|
||||
}
|
||||
|
||||
div#cgit div.diffstat-header {
|
||||
font-weight: bold;
|
||||
padding-top: 1.5em;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat {
|
||||
border-collapse: collapse;
|
||||
border: solid 1px #aaa;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat th {
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
text-decoration: underline;
|
||||
padding: 0.1em 1em 0.1em 0.1em;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td {
|
||||
padding: 0.2em 0.2em 0.1em 0.1em;
|
||||
font-size: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.mode {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td span.modechange {
|
||||
padding-left: 1em;
|
||||
color: red;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.add a {
|
||||
color: green;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.del a {
|
||||
color: red;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.upd a {
|
||||
color: blue;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.graph {
|
||||
width: 500px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.graph table {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.graph td {
|
||||
padding: 0px;
|
||||
border: 0px;
|
||||
height: 7pt;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.graph td.add {
|
||||
background-color: #5c5;
|
||||
}
|
||||
|
||||
div#cgit table.diffstat td.graph td.rem {
|
||||
background-color: #c55;
|
||||
}
|
||||
|
||||
div#cgit div.diffstat-summary {
|
||||
color: #888;
|
||||
padding-top: 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.diff {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#cgit table.diff td {
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
div#cgit table.diff td div.head {
|
||||
font-weight: bold;
|
||||
margin-top: 1em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#cgit table.diff td div.hunk {
|
||||
color: #009;
|
||||
}
|
||||
|
||||
div#cgit table.diff td div.add {
|
||||
color: green;
|
||||
}
|
||||
|
||||
div#cgit table.diff td div.del {
|
||||
color: red;
|
||||
}
|
||||
|
||||
div#cgit .sha1 {
|
||||
font-family: monospace;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div#cgit .left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div#cgit .right {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
div#cgit table.list td.reposection {
|
||||
font-style: italic;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div#cgit a.button {
|
||||
font-size: 80%;
|
||||
padding: 0em 0.5em;
|
||||
}
|
||||
|
||||
div#cgit a.primary {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
div#cgit a.secondary {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
div#cgit td.toplevel-repo {
|
||||
|
||||
}
|
||||
|
||||
div#cgit table.list td.sublevel-repo {
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
div#cgit ul.pager {
|
||||
list-style-type: none;
|
||||
text-align: center;
|
||||
margin: 1em 0em 0em 0em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
div#cgit ul.pager li {
|
||||
display: inline-block;
|
||||
margin: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
div#cgit ul.pager a {
|
||||
color: #777;
|
||||
}
|
||||
|
||||
div#cgit ul.pager .current {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div#cgit span.age-mins {
|
||||
font-weight: bold;
|
||||
color: #080;
|
||||
}
|
||||
|
||||
div#cgit span.age-hours {
|
||||
color: #080;
|
||||
}
|
||||
|
||||
div#cgit span.age-days {
|
||||
color: #040;
|
||||
}
|
||||
|
||||
div#cgit span.age-weeks {
|
||||
color: #444;
|
||||
}
|
||||
|
||||
div#cgit span.age-months {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
div#cgit span.age-years {
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
div#cgit span.insertions {
|
||||
color: #080;
|
||||
}
|
||||
|
||||
div#cgit span.deletions {
|
||||
color: #800;
|
||||
}
|
||||
|
||||
div#cgit div.footer {
|
||||
margin-top: 0.5em;
|
||||
text-align: center;
|
||||
font-size: 80%;
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
div#cgit div.footer a {
|
||||
color: #ccc;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
div#cgit div.footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
div#cgit a.branch-deco {
|
||||
color: #000;
|
||||
margin: 0px 0.5em;
|
||||
padding: 0px 0.25em;
|
||||
background-color: #88ff88;
|
||||
border: solid 1px #007700;
|
||||
}
|
||||
|
||||
div#cgit a.tag-deco {
|
||||
color: #000;
|
||||
margin: 0px 0.5em;
|
||||
padding: 0px 0.25em;
|
||||
background-color: #ffff88;
|
||||
border: solid 1px #777700;
|
||||
}
|
||||
|
||||
div#cgit a.tag-annotated-deco {
|
||||
color: #000;
|
||||
margin: 0px 0.5em;
|
||||
padding: 0px 0.25em;
|
||||
background-color: #ffcc88;
|
||||
border: solid 1px #777700;
|
||||
}
|
||||
|
||||
div#cgit a.remote-deco {
|
||||
color: #000;
|
||||
margin: 0px 0.5em;
|
||||
padding: 0px 0.25em;
|
||||
background-color: #ccccff;
|
||||
border: solid 1px #000077;
|
||||
}
|
||||
|
||||
div#cgit a.deco {
|
||||
color: #000;
|
||||
margin: 0px 0.5em;
|
||||
padding: 0px 0.25em;
|
||||
background-color: #ff8888;
|
||||
border: solid 1px #770000;
|
||||
}
|
||||
|
||||
div#cgit div.commit-subject a.branch-deco,
|
||||
div#cgit div.commit-subject a.tag-deco,
|
||||
div#cgit div.commit-subject a.tag-annotated-deco,
|
||||
div#cgit div.commit-subject a.remote-deco,
|
||||
div#cgit div.commit-subject a.deco {
|
||||
margin-left: 1em;
|
||||
font-size: 75%;
|
||||
}
|
||||
|
||||
div#cgit table.stats {
|
||||
border: solid 1px black;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
div#cgit table.stats th {
|
||||
text-align: left;
|
||||
padding: 1px 0.5em;
|
||||
background-color: #eee;
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
div#cgit table.stats td {
|
||||
text-align: right;
|
||||
padding: 1px 0.5em;
|
||||
border: solid 1px black;
|
||||
}
|
||||
|
||||
div#cgit table.stats td.total {
|
||||
font-weight: bold;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div#cgit table.stats td.sum {
|
||||
color: #c00;
|
||||
font-weight: bold;
|
||||
/* background-color: #eee; */
|
||||
}
|
||||
|
||||
div#cgit table.stats td.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
div#cgit table.vgraph {
|
||||
border-collapse: separate;
|
||||
border: solid 1px black;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
div#cgit table.vgraph th {
|
||||
background-color: #eee;
|
||||
font-weight: bold;
|
||||
border: solid 1px white;
|
||||
padding: 1px 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.vgraph td {
|
||||
vertical-align: bottom;
|
||||
padding: 0px 10px;
|
||||
}
|
||||
|
||||
div#cgit table.vgraph div.bar {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
div#cgit table.hgraph {
|
||||
border: solid 1px black;
|
||||
width: 800px;
|
||||
}
|
||||
|
||||
div#cgit table.hgraph th {
|
||||
background-color: #eee;
|
||||
font-weight: bold;
|
||||
border: solid 1px black;
|
||||
padding: 1px 0.5em;
|
||||
}
|
||||
|
||||
div#cgit table.hgraph td {
|
||||
vertical-align: middle;
|
||||
padding: 2px 2px;
|
||||
}
|
||||
|
||||
div#cgit table.hgraph div.bar {
|
||||
background-color: #eee;
|
||||
height: 1em;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td {
|
||||
font-size: 75%;
|
||||
font-family: monospace;
|
||||
white-space: pre;
|
||||
padding: 1px 4px 1px 4px;
|
||||
border-left: solid 1px #aaa;
|
||||
border-right: solid 1px #aaa;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.add {
|
||||
color: black;
|
||||
background: #cfc;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.add_dark {
|
||||
color: black;
|
||||
background: #aca;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff span.add {
|
||||
background: #cfc;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.del {
|
||||
color: black;
|
||||
background: #fcc;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.del_dark {
|
||||
color: black;
|
||||
background: #caa;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff span.del {
|
||||
background: #fcc;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.changed {
|
||||
color: black;
|
||||
background: #ffc;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.changed_dark {
|
||||
color: black;
|
||||
background: #cca;
|
||||
min-width: 50%;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.lineno {
|
||||
color: black;
|
||||
background: #eee;
|
||||
text-align: right;
|
||||
width: 3em;
|
||||
min-width: 3em;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.hunk {
|
||||
color: black;
|
||||
background: #ccf;
|
||||
border-top: solid 1px #aaa;
|
||||
border-bottom: solid 1px #aaa;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.head {
|
||||
border-top: solid 1px #aaa;
|
||||
border-bottom: solid 1px #aaa;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.head div.head {
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.foot {
|
||||
border-top: solid 1px #aaa;
|
||||
border-left: none;
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.space {
|
||||
border: none;
|
||||
}
|
||||
|
||||
div#cgit table.ssdiff td.space div {
|
||||
min-height: 3em;
|
||||
}
|
398
cgit.h
Normal file
398
cgit.h
Normal file
|
@ -0,0 +1,398 @@
|
|||
#ifndef CGIT_H
|
||||
#define CGIT_H
|
||||
|
||||
|
||||
#include <git-compat-util.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <cache.h>
|
||||
#include <grep.h>
|
||||
#include <object.h>
|
||||
#include <object-store.h>
|
||||
#include <tree.h>
|
||||
#include <commit.h>
|
||||
#include <tag.h>
|
||||
#include <diff.h>
|
||||
#include <diffcore.h>
|
||||
#include <argv-array.h>
|
||||
#include <refs.h>
|
||||
#include <revision.h>
|
||||
#include <log-tree.h>
|
||||
#include <archive.h>
|
||||
#include <string-list.h>
|
||||
#include <xdiff-interface.h>
|
||||
#include <xdiff/xdiff.h>
|
||||
#include <utf8.h>
|
||||
#include <notes.h>
|
||||
#include <graph.h>
|
||||
|
||||
/* Add isgraph(x) to Git's sane ctype support (see git-compat-util.h) */
|
||||
#undef isgraph
|
||||
#define isgraph(x) (isprint((x)) && !isspace((x)))
|
||||
|
||||
|
||||
/*
|
||||
* Limits used for relative dates
|
||||
*/
|
||||
#define TM_MIN 60
|
||||
#define TM_HOUR (TM_MIN * 60)
|
||||
#define TM_DAY (TM_HOUR * 24)
|
||||
#define TM_WEEK (TM_DAY * 7)
|
||||
#define TM_YEAR (TM_DAY * 365)
|
||||
#define TM_MONTH (TM_YEAR / 12.0)
|
||||
|
||||
|
||||
/*
|
||||
* Default encoding
|
||||
*/
|
||||
#define PAGE_ENCODING "UTF-8"
|
||||
|
||||
#define BIT(x) (1U << (x))
|
||||
|
||||
typedef void (*configfn)(const char *name, const char *value);
|
||||
typedef void (*filepair_fn)(struct diff_filepair *pair);
|
||||
typedef void (*linediff_fn)(char *line, int len);
|
||||
|
||||
typedef enum {
|
||||
DIFF_UNIFIED, DIFF_SSDIFF, DIFF_STATONLY
|
||||
} diff_type;
|
||||
|
||||
typedef enum {
|
||||
ABOUT, COMMIT, SOURCE, EMAIL, AUTH, OWNER
|
||||
} filter_type;
|
||||
|
||||
struct cgit_filter {
|
||||
int (*open)(struct cgit_filter *, va_list ap);
|
||||
int (*close)(struct cgit_filter *);
|
||||
void (*fprintf)(struct cgit_filter *, FILE *, const char *prefix);
|
||||
void (*cleanup)(struct cgit_filter *);
|
||||
int argument_count;
|
||||
};
|
||||
|
||||
struct cgit_exec_filter {
|
||||
struct cgit_filter base;
|
||||
char *cmd;
|
||||
char **argv;
|
||||
int old_stdout;
|
||||
int pid;
|
||||
};
|
||||
|
||||
struct cgit_repo {
|
||||
char *url;
|
||||
char *name;
|
||||
char *path;
|
||||
char *desc;
|
||||
char *extra_head_content;
|
||||
char *owner;
|
||||
char *homepage;
|
||||
char *defbranch;
|
||||
char *module_link;
|
||||
struct string_list readme;
|
||||
char *section;
|
||||
char *clone_url;
|
||||
char *logo;
|
||||
char *logo_link;
|
||||
char *snapshot_prefix;
|
||||
int snapshots;
|
||||
int enable_blame;
|
||||
int enable_commit_graph;
|
||||
int enable_log_filecount;
|
||||
int enable_log_linecount;
|
||||
int enable_remote_branches;
|
||||
int enable_subject_links;
|
||||
int enable_html_serving;
|
||||
int max_stats;
|
||||
int branch_sort;
|
||||
int commit_sort;
|
||||
time_t mtime;
|
||||
struct cgit_filter *about_filter;
|
||||
struct cgit_filter *commit_filter;
|
||||
struct cgit_filter *source_filter;
|
||||
struct cgit_filter *email_filter;
|
||||
struct cgit_filter *owner_filter;
|
||||
struct string_list submodules;
|
||||
int hide;
|
||||
int ignore;
|
||||
};
|
||||
|
||||
typedef void (*repo_config_fn)(struct cgit_repo *repo, const char *name,
|
||||
const char *value);
|
||||
|
||||
struct cgit_repolist {
|
||||
int length;
|
||||
int count;
|
||||
struct cgit_repo *repos;
|
||||
};
|
||||
|
||||
struct commitinfo {
|
||||
struct commit *commit;
|
||||
char *author;
|
||||
char *author_email;
|
||||
unsigned long author_date;
|
||||
int author_tz;
|
||||
char *committer;
|
||||
char *committer_email;
|
||||
unsigned long committer_date;
|
||||
int committer_tz;
|
||||
char *subject;
|
||||
char *msg;
|
||||
char *msg_encoding;
|
||||
};
|
||||
|
||||
struct taginfo {
|
||||
char *tagger;
|
||||
char *tagger_email;
|
||||
unsigned long tagger_date;
|
||||
int tagger_tz;
|
||||
char *msg;
|
||||
};
|
||||
|
||||
struct refinfo {
|
||||
const char *refname;
|
||||
struct object *object;
|
||||
union {
|
||||
struct taginfo *tag;
|
||||
struct commitinfo *commit;
|
||||
};
|
||||
};
|
||||
|
||||
struct reflist {
|
||||
struct refinfo **refs;
|
||||
int alloc;
|
||||
int count;
|
||||
};
|
||||
|
||||
struct cgit_query {
|
||||
int has_symref;
|
||||
int has_sha1;
|
||||
int has_difftype;
|
||||
char *raw;
|
||||
char *repo;
|
||||
char *page;
|
||||
char *search;
|
||||
char *grep;
|
||||
char *head;
|
||||
char *sha1;
|
||||
char *sha2;
|
||||
char *path;
|
||||
char *name;
|
||||
char *url;
|
||||
char *period;
|
||||
int ofs;
|
||||
int nohead;
|
||||
char *sort;
|
||||
int showmsg;
|
||||
diff_type difftype;
|
||||
int show_all;
|
||||
int context;
|
||||
int ignorews;
|
||||
int follow;
|
||||
char *vpath;
|
||||
};
|
||||
|
||||
struct cgit_config {
|
||||
char *agefile;
|
||||
char *cache_root;
|
||||
char *clone_prefix;
|
||||
char *clone_url;
|
||||
char *css;
|
||||
char *favicon;
|
||||
char *footer;
|
||||
char *head_include;
|
||||
char *header;
|
||||
char *logo;
|
||||
char *logo_link;
|
||||
char *mimetype_file;
|
||||
char *module_link;
|
||||
char *project_list;
|
||||
struct string_list readme;
|
||||
char *robots;
|
||||
char *root_title;
|
||||
char *root_desc;
|
||||
char *root_readme;
|
||||
char *script_name;
|
||||
char *section;
|
||||
char *repository_sort;
|
||||
char *virtual_root; /* Always ends with '/'. */
|
||||
char *strict_export;
|
||||
int cache_size;
|
||||
int cache_dynamic_ttl;
|
||||
int cache_max_create_time;
|
||||
int cache_repo_ttl;
|
||||
int cache_root_ttl;
|
||||
int cache_scanrc_ttl;
|
||||
int cache_static_ttl;
|
||||
int cache_about_ttl;
|
||||
int cache_snapshot_ttl;
|
||||
int case_sensitive_sort;
|
||||
int embedded;
|
||||
int enable_filter_overrides;
|
||||
int enable_follow_links;
|
||||
int enable_http_clone;
|
||||
int enable_index_links;
|
||||
int enable_index_owner;
|
||||
int enable_blame;
|
||||
int enable_commit_graph;
|
||||
int enable_log_filecount;
|
||||
int enable_log_linecount;
|
||||
int enable_remote_branches;
|
||||
int enable_subject_links;
|
||||
int enable_html_serving;
|
||||
int enable_tree_linenumbers;
|
||||
int enable_git_config;
|
||||
int local_time;
|
||||
int max_atom_items;
|
||||
int max_repo_count;
|
||||
int max_commit_count;
|
||||
int max_lock_attempts;
|
||||
int max_msg_len;
|
||||
int max_repodesc_len;
|
||||
int max_blob_size;
|
||||
int max_stats;
|
||||
int noplainemail;
|
||||
int noheader;
|
||||
int renamelimit;
|
||||
int remove_suffix;
|
||||
int scan_hidden_path;
|
||||
int section_from_path;
|
||||
int snapshots;
|
||||
int section_sort;
|
||||
int summary_branches;
|
||||
int summary_log;
|
||||
int summary_tags;
|
||||
diff_type difftype;
|
||||
int branch_sort;
|
||||
int commit_sort;
|
||||
struct string_list mimetypes;
|
||||
struct cgit_filter *about_filter;
|
||||
struct cgit_filter *commit_filter;
|
||||
struct cgit_filter *source_filter;
|
||||
struct cgit_filter *email_filter;
|
||||
struct cgit_filter *owner_filter;
|
||||
struct cgit_filter *auth_filter;
|
||||
};
|
||||
|
||||
struct cgit_page {
|
||||
time_t modified;
|
||||
time_t expires;
|
||||
size_t size;
|
||||
const char *mimetype;
|
||||
const char *charset;
|
||||
const char *filename;
|
||||
const char *etag;
|
||||
const char *title;
|
||||
int status;
|
||||
const char *statusmsg;
|
||||
};
|
||||
|
||||
struct cgit_environment {
|
||||
const char *cgit_config;
|
||||
const char *http_host;
|
||||
const char *https;
|
||||
const char *no_http;
|
||||
const char *path_info;
|
||||
const char *query_string;
|
||||
const char *request_method;
|
||||
const char *script_name;
|
||||
const char *server_name;
|
||||
const char *server_port;
|
||||
const char *http_cookie;
|
||||
const char *http_referer;
|
||||
unsigned int content_length;
|
||||
int authenticated;
|
||||
};
|
||||
|
||||
struct cgit_context {
|
||||
struct cgit_environment env;
|
||||
struct cgit_query qry;
|
||||
struct cgit_config cfg;
|
||||
struct cgit_repo *repo;
|
||||
struct cgit_page page;
|
||||
};
|
||||
|
||||
typedef int (*write_archive_fn_t)(const char *, const char *);
|
||||
|
||||
struct cgit_snapshot_format {
|
||||
const char *suffix;
|
||||
const char *mimetype;
|
||||
write_archive_fn_t write_func;
|
||||
};
|
||||
|
||||
extern const char *cgit_version;
|
||||
|
||||
extern struct cgit_repolist cgit_repolist;
|
||||
extern struct cgit_context ctx;
|
||||
extern const struct cgit_snapshot_format cgit_snapshot_formats[];
|
||||
|
||||
extern char *cgit_default_repo_desc;
|
||||
extern struct cgit_repo *cgit_add_repo(const char *url);
|
||||
extern struct cgit_repo *cgit_get_repoinfo(const char *url);
|
||||
extern void cgit_repo_config_cb(const char *name, const char *value);
|
||||
|
||||
extern int chk_zero(int result, char *msg);
|
||||
extern int chk_positive(int result, char *msg);
|
||||
extern int chk_non_negative(int result, char *msg);
|
||||
|
||||
extern char *trim_end(const char *str, char c);
|
||||
extern char *ensure_end(const char *str, char c);
|
||||
|
||||
extern void strbuf_ensure_end(struct strbuf *sb, char c);
|
||||
|
||||
extern void cgit_add_ref(struct reflist *list, struct refinfo *ref);
|
||||
extern void cgit_free_reflist_inner(struct reflist *list);
|
||||
extern int cgit_refs_cb(const char *refname, const struct object_id *oid,
|
||||
int flags, void *cb_data);
|
||||
|
||||
extern void cgit_free_commitinfo(struct commitinfo *info);
|
||||
extern void cgit_free_taginfo(struct taginfo *info);
|
||||
|
||||
void cgit_diff_tree_cb(struct diff_queue_struct *q,
|
||||
struct diff_options *options, void *data);
|
||||
|
||||
extern int cgit_diff_files(const struct object_id *old_oid,
|
||||
const struct object_id *new_oid,
|
||||
unsigned long *old_size, unsigned long *new_size,
|
||||
int *binary, int context, int ignorews,
|
||||
linediff_fn fn);
|
||||
|
||||
extern void cgit_diff_tree(const struct object_id *old_oid,
|
||||
const struct object_id *new_oid,
|
||||
filepair_fn fn, const char *prefix, int ignorews);
|
||||
|
||||
extern void cgit_diff_commit(struct commit *commit, filepair_fn fn,
|
||||
const char *prefix);
|
||||
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern char *fmt(const char *format,...);
|
||||
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern char *fmtalloc(const char *format,...);
|
||||
|
||||
extern struct commitinfo *cgit_parse_commit(struct commit *commit);
|
||||
extern struct taginfo *cgit_parse_tag(struct tag *tag);
|
||||
extern void cgit_parse_url(const char *url);
|
||||
|
||||
extern const char *cgit_repobasename(const char *reponame);
|
||||
|
||||
extern int cgit_parse_snapshots_mask(const char *str);
|
||||
extern const struct object_id *cgit_snapshot_get_sig(const char *ref,
|
||||
const struct cgit_snapshot_format *f);
|
||||
extern const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f);
|
||||
|
||||
extern int cgit_open_filter(struct cgit_filter *filter, ...);
|
||||
extern int cgit_close_filter(struct cgit_filter *filter);
|
||||
extern void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix);
|
||||
extern void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv);
|
||||
extern struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype);
|
||||
extern void cgit_cleanup_filters(void);
|
||||
extern void cgit_init_filters(void);
|
||||
|
||||
extern void cgit_prepare_repo_env(struct cgit_repo * repo);
|
||||
|
||||
extern int readfile(const char *path, char **buf, size_t *size);
|
||||
|
||||
extern char *expand_macros(const char *txt);
|
||||
|
||||
extern char *get_mimetype_for_filename(const char *filename);
|
||||
|
||||
#endif /* CGIT_H */
|
141
cgit.mk
Normal file
141
cgit.mk
Normal file
|
@ -0,0 +1,141 @@
|
|||
# This Makefile is run in the "git" directory in order to re-use Git's
|
||||
# build variables and operating system detection. Hence all files in
|
||||
# CGit's directory must be prefixed with "../".
|
||||
include Makefile
|
||||
|
||||
CGIT_PREFIX = ../
|
||||
|
||||
-include $(CGIT_PREFIX)cgit.conf
|
||||
|
||||
# The CGIT_* variables are inherited when this file is called from the
|
||||
# main Makefile - they are defined there.
|
||||
|
||||
$(CGIT_PREFIX)VERSION: force-version
|
||||
@cd $(CGIT_PREFIX) && '$(SHELL_PATH_SQ)' ./gen-version.sh "$(CGIT_VERSION)"
|
||||
-include $(CGIT_PREFIX)VERSION
|
||||
.PHONY: force-version
|
||||
|
||||
# CGIT_CFLAGS is a separate variable so that we can track it separately
|
||||
# and avoid rebuilding all of Git when these variables change.
|
||||
CGIT_CFLAGS += -DCGIT_CONFIG='"$(CGIT_CONFIG)"'
|
||||
CGIT_CFLAGS += -DCGIT_SCRIPT_NAME='"$(CGIT_SCRIPT_NAME)"'
|
||||
CGIT_CFLAGS += -DCGIT_CACHE_ROOT='"$(CACHE_ROOT)"'
|
||||
|
||||
PKG_CONFIG ?= pkg-config
|
||||
|
||||
ifdef NO_C99_FORMAT
|
||||
CFLAGS += -DNO_C99_FORMAT
|
||||
endif
|
||||
|
||||
ifdef NO_LUA
|
||||
LUA_MESSAGE := linking without specified Lua support
|
||||
CGIT_CFLAGS += -DNO_LUA
|
||||
else
|
||||
ifeq ($(LUA_PKGCONFIG),)
|
||||
LUA_PKGCONFIG := $(shell for pc in luajit lua lua5.2 lua5.1; do \
|
||||
$(PKG_CONFIG) --exists $$pc 2>/dev/null && echo $$pc && break; \
|
||||
done)
|
||||
LUA_MODE := autodetected
|
||||
else
|
||||
LUA_MODE := specified
|
||||
endif
|
||||
ifneq ($(LUA_PKGCONFIG),)
|
||||
LUA_MESSAGE := linking with $(LUA_MODE) $(LUA_PKGCONFIG)
|
||||
LUA_LIBS := $(shell $(PKG_CONFIG) --libs $(LUA_PKGCONFIG) 2>/dev/null)
|
||||
LUA_CFLAGS := $(shell $(PKG_CONFIG) --cflags $(LUA_PKGCONFIG) 2>/dev/null)
|
||||
CGIT_LIBS += $(LUA_LIBS)
|
||||
CGIT_CFLAGS += $(LUA_CFLAGS)
|
||||
else
|
||||
LUA_MESSAGE := linking without autodetected Lua support
|
||||
NO_LUA := YesPlease
|
||||
CGIT_CFLAGS += -DNO_LUA
|
||||
endif
|
||||
|
||||
endif
|
||||
|
||||
# Add -ldl to linker flags on systems that commonly use GNU libc.
|
||||
ifneq (,$(filter $(uname_S),Linux GNU GNU/kFreeBSD))
|
||||
CGIT_LIBS += -ldl
|
||||
endif
|
||||
|
||||
# glibc 2.1+ offers sendfile which the most common C library on Linux
|
||||
ifeq ($(uname_S),Linux)
|
||||
HAVE_LINUX_SENDFILE = YesPlease
|
||||
endif
|
||||
|
||||
ifdef HAVE_LINUX_SENDFILE
|
||||
CGIT_CFLAGS += -DHAVE_LINUX_SENDFILE
|
||||
endif
|
||||
|
||||
CGIT_OBJ_NAMES += cgit.o
|
||||
CGIT_OBJ_NAMES += cache.o
|
||||
CGIT_OBJ_NAMES += cmd.o
|
||||
CGIT_OBJ_NAMES += configfile.o
|
||||
CGIT_OBJ_NAMES += filter.o
|
||||
CGIT_OBJ_NAMES += html.o
|
||||
CGIT_OBJ_NAMES += parsing.o
|
||||
CGIT_OBJ_NAMES += scan-tree.o
|
||||
CGIT_OBJ_NAMES += shared.o
|
||||
CGIT_OBJ_NAMES += ui-atom.o
|
||||
CGIT_OBJ_NAMES += ui-blame.o
|
||||
CGIT_OBJ_NAMES += ui-blob.o
|
||||
CGIT_OBJ_NAMES += ui-clone.o
|
||||
CGIT_OBJ_NAMES += ui-commit.o
|
||||
CGIT_OBJ_NAMES += ui-diff.o
|
||||
CGIT_OBJ_NAMES += ui-log.o
|
||||
CGIT_OBJ_NAMES += ui-patch.o
|
||||
CGIT_OBJ_NAMES += ui-plain.o
|
||||
CGIT_OBJ_NAMES += ui-refs.o
|
||||
CGIT_OBJ_NAMES += ui-repolist.o
|
||||
CGIT_OBJ_NAMES += ui-shared.o
|
||||
CGIT_OBJ_NAMES += ui-snapshot.o
|
||||
CGIT_OBJ_NAMES += ui-ssdiff.o
|
||||
CGIT_OBJ_NAMES += ui-stats.o
|
||||
CGIT_OBJ_NAMES += ui-summary.o
|
||||
CGIT_OBJ_NAMES += ui-tag.o
|
||||
CGIT_OBJ_NAMES += ui-tree.o
|
||||
|
||||
CGIT_OBJS := $(addprefix $(CGIT_PREFIX),$(CGIT_OBJ_NAMES))
|
||||
|
||||
# Only cgit.c reference CGIT_VERSION so we only rebuild its objects when the
|
||||
# version changes.
|
||||
CGIT_VERSION_OBJS := $(addprefix $(CGIT_PREFIX),cgit.o cgit.sp)
|
||||
$(CGIT_VERSION_OBJS): $(CGIT_PREFIX)VERSION
|
||||
$(CGIT_VERSION_OBJS): EXTRA_CPPFLAGS = \
|
||||
-DCGIT_VERSION='"$(CGIT_VERSION)"'
|
||||
|
||||
# Git handles dependencies using ":=" so dependencies in CGIT_OBJ are not
|
||||
# handled by that and we must handle them ourselves.
|
||||
cgit_dep_files := $(foreach f,$(CGIT_OBJS),$(dir $f).depend/$(notdir $f).d)
|
||||
cgit_dep_files_present := $(wildcard $(cgit_dep_files))
|
||||
ifneq ($(cgit_dep_files_present),)
|
||||
include $(cgit_dep_files_present)
|
||||
endif
|
||||
|
||||
ifeq ($(wildcard $(CGIT_PREFIX).depend),)
|
||||
missing_dep_dirs += $(CGIT_PREFIX).depend
|
||||
endif
|
||||
|
||||
$(CGIT_PREFIX).depend:
|
||||
@mkdir -p $@
|
||||
|
||||
$(CGIT_PREFIX)CGIT-CFLAGS: FORCE
|
||||
@FLAGS='$(subst ','\'',$(CGIT_CFLAGS))'; \
|
||||
if test x"$$FLAGS" != x"`cat ../CGIT-CFLAGS 2>/dev/null`" ; then \
|
||||
echo 1>&2 " * new CGit build flags"; \
|
||||
echo "$$FLAGS" >$(CGIT_PREFIX)CGIT-CFLAGS; \
|
||||
fi
|
||||
|
||||
$(CGIT_OBJS): %.o: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS $(missing_dep_dirs)
|
||||
$(QUIET_CC)$(CC) -o $*.o -c $(dep_args) $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $<
|
||||
|
||||
$(CGIT_PREFIX)cgit: $(CGIT_OBJS) GIT-LDFLAGS $(GITLIBS)
|
||||
@echo 1>&1 " * $(LUA_MESSAGE)"
|
||||
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) $(CGIT_LIBS)
|
||||
|
||||
CGIT_SP_OBJS := $(patsubst %.o,%.sp,$(CGIT_OBJS))
|
||||
|
||||
$(CGIT_SP_OBJS): %.sp: %.c GIT-CFLAGS $(CGIT_PREFIX)CGIT-CFLAGS FORCE
|
||||
$(QUIET_SP)cgcc -no-compile $(ALL_CFLAGS) $(EXTRA_CPPFLAGS) $(CGIT_CFLAGS) $(SPARSE_FLAGS) $<
|
||||
|
||||
cgit-sparse: $(CGIT_SP_OBJS)
|
BIN
cgit.png
Normal file
BIN
cgit.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
1008
cgitrc.5.txt
Normal file
1008
cgitrc.5.txt
Normal file
File diff suppressed because it is too large
Load diff
208
cmd.c
Normal file
208
cmd.c
Normal file
|
@ -0,0 +1,208 @@
|
|||
/* cmd.c: the cgit command dispatcher
|
||||
*
|
||||
* Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "cmd.h"
|
||||
#include "cache.h"
|
||||
#include "ui-shared.h"
|
||||
#include "ui-atom.h"
|
||||
#include "ui-blame.h"
|
||||
#include "ui-blob.h"
|
||||
#include "ui-clone.h"
|
||||
#include "ui-commit.h"
|
||||
#include "ui-diff.h"
|
||||
#include "ui-log.h"
|
||||
#include "ui-patch.h"
|
||||
#include "ui-plain.h"
|
||||
#include "ui-refs.h"
|
||||
#include "ui-repolist.h"
|
||||
#include "ui-snapshot.h"
|
||||
#include "ui-stats.h"
|
||||
#include "ui-summary.h"
|
||||
#include "ui-tag.h"
|
||||
#include "ui-tree.h"
|
||||
|
||||
static void HEAD_fn(void)
|
||||
{
|
||||
cgit_clone_head();
|
||||
}
|
||||
|
||||
static void atom_fn(void)
|
||||
{
|
||||
cgit_print_atom(ctx.qry.head, ctx.qry.path, ctx.cfg.max_atom_items);
|
||||
}
|
||||
|
||||
static void about_fn(void)
|
||||
{
|
||||
if (ctx.repo) {
|
||||
size_t path_info_len = ctx.env.path_info ? strlen(ctx.env.path_info) : 0;
|
||||
if (!ctx.qry.path &&
|
||||
ctx.qry.url[strlen(ctx.qry.url) - 1] != '/' &&
|
||||
(!path_info_len || ctx.env.path_info[path_info_len - 1] != '/')) {
|
||||
char *currenturl = cgit_currenturl();
|
||||
char *redirect = fmtalloc("%s/", currenturl);
|
||||
cgit_redirect(redirect, true);
|
||||
free(currenturl);
|
||||
free(redirect);
|
||||
} else if (ctx.repo->readme.nr)
|
||||
cgit_print_repo_readme(ctx.qry.path);
|
||||
else if (ctx.repo->homepage)
|
||||
cgit_redirect(ctx.repo->homepage, false);
|
||||
else {
|
||||
char *currenturl = cgit_currenturl();
|
||||
char *redirect = fmtalloc("%s../", currenturl);
|
||||
cgit_redirect(redirect, false);
|
||||
free(currenturl);
|
||||
free(redirect);
|
||||
}
|
||||
} else
|
||||
cgit_print_site_readme();
|
||||
}
|
||||
|
||||
static void blame_fn(void)
|
||||
{
|
||||
if (ctx.repo->enable_blame)
|
||||
cgit_print_blame();
|
||||
else
|
||||
cgit_print_error_page(403, "Forbidden", "Blame is disabled");
|
||||
}
|
||||
|
||||
static void blob_fn(void)
|
||||
{
|
||||
cgit_print_blob(ctx.qry.sha1, ctx.qry.path, ctx.qry.head, 0);
|
||||
}
|
||||
|
||||
static void commit_fn(void)
|
||||
{
|
||||
cgit_print_commit(ctx.qry.sha1, ctx.qry.path);
|
||||
}
|
||||
|
||||
static void diff_fn(void)
|
||||
{
|
||||
cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 0);
|
||||
}
|
||||
|
||||
static void rawdiff_fn(void)
|
||||
{
|
||||
cgit_print_diff(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path, 1, 1);
|
||||
}
|
||||
|
||||
static void info_fn(void)
|
||||
{
|
||||
cgit_clone_info();
|
||||
}
|
||||
|
||||
static void log_fn(void)
|
||||
{
|
||||
cgit_print_log(ctx.qry.sha1, ctx.qry.ofs, ctx.cfg.max_commit_count,
|
||||
ctx.qry.grep, ctx.qry.search, ctx.qry.path, 1,
|
||||
ctx.repo->enable_commit_graph,
|
||||
ctx.repo->commit_sort);
|
||||
}
|
||||
|
||||
static void ls_cache_fn(void)
|
||||
{
|
||||
ctx.page.mimetype = "text/plain";
|
||||
ctx.page.filename = "ls-cache.txt";
|
||||
cgit_print_http_headers();
|
||||
cache_ls(ctx.cfg.cache_root);
|
||||
}
|
||||
|
||||
static void objects_fn(void)
|
||||
{
|
||||
cgit_clone_objects();
|
||||
}
|
||||
|
||||
static void repolist_fn(void)
|
||||
{
|
||||
cgit_print_repolist();
|
||||
}
|
||||
|
||||
static void patch_fn(void)
|
||||
{
|
||||
cgit_print_patch(ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path);
|
||||
}
|
||||
|
||||
static void plain_fn(void)
|
||||
{
|
||||
cgit_print_plain();
|
||||
}
|
||||
|
||||
static void refs_fn(void)
|
||||
{
|
||||
cgit_print_refs();
|
||||
}
|
||||
|
||||
static void snapshot_fn(void)
|
||||
{
|
||||
cgit_print_snapshot(ctx.qry.head, ctx.qry.sha1, ctx.qry.path,
|
||||
ctx.qry.nohead);
|
||||
}
|
||||
|
||||
static void stats_fn(void)
|
||||
{
|
||||
cgit_show_stats();
|
||||
}
|
||||
|
||||
static void summary_fn(void)
|
||||
{
|
||||
cgit_print_summary();
|
||||
}
|
||||
|
||||
static void tag_fn(void)
|
||||
{
|
||||
cgit_print_tag(ctx.qry.sha1);
|
||||
}
|
||||
|
||||
static void tree_fn(void)
|
||||
{
|
||||
cgit_print_tree(ctx.qry.sha1, ctx.qry.path);
|
||||
}
|
||||
|
||||
#define def_cmd(name, want_repo, want_vpath, is_clone) \
|
||||
{#name, name##_fn, want_repo, want_vpath, is_clone}
|
||||
|
||||
struct cgit_cmd *cgit_get_cmd(void)
|
||||
{
|
||||
static struct cgit_cmd cmds[] = {
|
||||
def_cmd(HEAD, 1, 0, 1),
|
||||
def_cmd(atom, 1, 0, 0),
|
||||
def_cmd(about, 0, 0, 0),
|
||||
def_cmd(blame, 1, 1, 0),
|
||||
def_cmd(blob, 1, 0, 0),
|
||||
def_cmd(commit, 1, 1, 0),
|
||||
def_cmd(diff, 1, 1, 0),
|
||||
def_cmd(info, 1, 0, 1),
|
||||
def_cmd(log, 1, 1, 0),
|
||||
def_cmd(ls_cache, 0, 0, 0),
|
||||
def_cmd(objects, 1, 0, 1),
|
||||
def_cmd(patch, 1, 1, 0),
|
||||
def_cmd(plain, 1, 0, 0),
|
||||
def_cmd(rawdiff, 1, 1, 0),
|
||||
def_cmd(refs, 1, 0, 0),
|
||||
def_cmd(repolist, 0, 0, 0),
|
||||
def_cmd(snapshot, 1, 0, 0),
|
||||
def_cmd(stats, 1, 1, 0),
|
||||
def_cmd(summary, 1, 0, 0),
|
||||
def_cmd(tag, 1, 0, 0),
|
||||
def_cmd(tree, 1, 1, 0),
|
||||
};
|
||||
int i;
|
||||
|
||||
if (ctx.qry.page == NULL) {
|
||||
if (ctx.repo)
|
||||
ctx.qry.page = "summary";
|
||||
else
|
||||
ctx.qry.page = "repolist";
|
||||
}
|
||||
|
||||
for (i = 0; i < sizeof(cmds)/sizeof(*cmds); i++)
|
||||
if (!strcmp(ctx.qry.page, cmds[i].name))
|
||||
return &cmds[i];
|
||||
return NULL;
|
||||
}
|
16
cmd.h
Normal file
16
cmd.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#ifndef CMD_H
|
||||
#define CMD_H
|
||||
|
||||
typedef void (*cgit_cmd_fn)(void);
|
||||
|
||||
struct cgit_cmd {
|
||||
const char *name;
|
||||
cgit_cmd_fn fn;
|
||||
unsigned int want_repo:1,
|
||||
want_vpath:1,
|
||||
is_clone:1;
|
||||
};
|
||||
|
||||
extern struct cgit_cmd *cgit_get_cmd(void);
|
||||
|
||||
#endif /* CMD_H */
|
90
configfile.c
Normal file
90
configfile.c
Normal file
|
@ -0,0 +1,90 @@
|
|||
/* configfile.c: parsing of config files
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include <git-compat-util.h>
|
||||
#include "configfile.h"
|
||||
|
||||
static int next_char(FILE *f)
|
||||
{
|
||||
int c = fgetc(f);
|
||||
if (c == '\r') {
|
||||
c = fgetc(f);
|
||||
if (c != '\n') {
|
||||
ungetc(c, f);
|
||||
c = '\r';
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void skip_line(FILE *f)
|
||||
{
|
||||
int c;
|
||||
|
||||
while ((c = next_char(f)) && c != '\n' && c != EOF)
|
||||
;
|
||||
}
|
||||
|
||||
static int read_config_line(FILE *f, struct strbuf *name, struct strbuf *value)
|
||||
{
|
||||
int c = next_char(f);
|
||||
|
||||
strbuf_reset(name);
|
||||
strbuf_reset(value);
|
||||
|
||||
/* Skip comments and preceding spaces. */
|
||||
for(;;) {
|
||||
if (c == EOF)
|
||||
return 0;
|
||||
else if (c == '#' || c == ';')
|
||||
skip_line(f);
|
||||
else if (!isspace(c))
|
||||
break;
|
||||
c = next_char(f);
|
||||
}
|
||||
|
||||
/* Read variable name. */
|
||||
while (c != '=') {
|
||||
if (c == '\n' || c == EOF)
|
||||
return 0;
|
||||
strbuf_addch(name, c);
|
||||
c = next_char(f);
|
||||
}
|
||||
|
||||
/* Read variable value. */
|
||||
c = next_char(f);
|
||||
while (c != '\n' && c != EOF) {
|
||||
strbuf_addch(value, c);
|
||||
c = next_char(f);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int parse_configfile(const char *filename, configfile_value_fn fn)
|
||||
{
|
||||
static int nesting;
|
||||
struct strbuf name = STRBUF_INIT;
|
||||
struct strbuf value = STRBUF_INIT;
|
||||
FILE *f;
|
||||
|
||||
/* cancel deeply nested include-commands */
|
||||
if (nesting > 8)
|
||||
return -1;
|
||||
if (!(f = fopen(filename, "r")))
|
||||
return -1;
|
||||
nesting++;
|
||||
while (read_config_line(f, &name, &value))
|
||||
fn(name.buf, value.buf);
|
||||
nesting--;
|
||||
fclose(f);
|
||||
strbuf_release(&name);
|
||||
strbuf_release(&value);
|
||||
return 0;
|
||||
}
|
||||
|
10
configfile.h
Normal file
10
configfile.h
Normal file
|
@ -0,0 +1,10 @@
|
|||
#ifndef CONFIGFILE_H
|
||||
#define CONFIGFILE_H
|
||||
|
||||
#include "cgit.h"
|
||||
|
||||
typedef void (*configfile_value_fn)(const char *name, const char *value);
|
||||
|
||||
extern int parse_configfile(const char *filename, configfile_value_fn fn);
|
||||
|
||||
#endif /* CONFIGFILE_H */
|
19
contrib/hooks/post-receive.agefile
Executable file
19
contrib/hooks/post-receive.agefile
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# An example hook to update the "agefile" for CGit's idle time calculation.
|
||||
#
|
||||
# This hook assumes that you are using the default agefile location of
|
||||
# "info/web/last-modified". If you change the value in your cgitrc then you
|
||||
# must also change it here.
|
||||
#
|
||||
# To install the hook, copy (or link) it to the file "hooks/post-receive" in
|
||||
# each of your repositories.
|
||||
#
|
||||
|
||||
agefile="$(git rev-parse --git-dir)"/info/web/last-modified
|
||||
|
||||
mkdir -p "$(dirname "$agefile")" &&
|
||||
git for-each-ref \
|
||||
--sort=-authordate --count=1 \
|
||||
--format='%(authordate:iso8601)' \
|
||||
>"$agefile"
|
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
457
filter.c
Normal file
457
filter.c
Normal file
|
@ -0,0 +1,457 @@
|
|||
/* filter.c: filter framework functions
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "html.h"
|
||||
#ifndef NO_LUA
|
||||
#include <dlfcn.h>
|
||||
#include <lua.h>
|
||||
#include <lualib.h>
|
||||
#include <lauxlib.h>
|
||||
#endif
|
||||
|
||||
static inline void reap_filter(struct cgit_filter *filter)
|
||||
{
|
||||
if (filter && filter->cleanup)
|
||||
filter->cleanup(filter);
|
||||
}
|
||||
|
||||
void cgit_cleanup_filters(void)
|
||||
{
|
||||
int i;
|
||||
reap_filter(ctx.cfg.about_filter);
|
||||
reap_filter(ctx.cfg.commit_filter);
|
||||
reap_filter(ctx.cfg.source_filter);
|
||||
reap_filter(ctx.cfg.email_filter);
|
||||
reap_filter(ctx.cfg.owner_filter);
|
||||
reap_filter(ctx.cfg.auth_filter);
|
||||
for (i = 0; i < cgit_repolist.count; ++i) {
|
||||
reap_filter(cgit_repolist.repos[i].about_filter);
|
||||
reap_filter(cgit_repolist.repos[i].commit_filter);
|
||||
reap_filter(cgit_repolist.repos[i].source_filter);
|
||||
reap_filter(cgit_repolist.repos[i].email_filter);
|
||||
reap_filter(cgit_repolist.repos[i].owner_filter);
|
||||
}
|
||||
}
|
||||
|
||||
static int open_exec_filter(struct cgit_filter *base, va_list ap)
|
||||
{
|
||||
struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
|
||||
int pipe_fh[2];
|
||||
int i;
|
||||
|
||||
for (i = 0; i < filter->base.argument_count; i++)
|
||||
filter->argv[i + 1] = va_arg(ap, char *);
|
||||
|
||||
filter->old_stdout = chk_positive(dup(STDOUT_FILENO),
|
||||
"Unable to duplicate STDOUT");
|
||||
chk_zero(pipe(pipe_fh), "Unable to create pipe to subprocess");
|
||||
filter->pid = chk_non_negative(fork(), "Unable to create subprocess");
|
||||
if (filter->pid == 0) {
|
||||
close(pipe_fh[1]);
|
||||
chk_non_negative(dup2(pipe_fh[0], STDIN_FILENO),
|
||||
"Unable to use pipe as STDIN");
|
||||
execvp(filter->cmd, filter->argv);
|
||||
die_errno("Unable to exec subprocess %s", filter->cmd);
|
||||
}
|
||||
close(pipe_fh[0]);
|
||||
chk_non_negative(dup2(pipe_fh[1], STDOUT_FILENO),
|
||||
"Unable to use pipe as STDOUT");
|
||||
close(pipe_fh[1]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int close_exec_filter(struct cgit_filter *base)
|
||||
{
|
||||
struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
|
||||
int i, exit_status = 0;
|
||||
|
||||
chk_non_negative(dup2(filter->old_stdout, STDOUT_FILENO),
|
||||
"Unable to restore STDOUT");
|
||||
close(filter->old_stdout);
|
||||
if (filter->pid < 0)
|
||||
goto done;
|
||||
waitpid(filter->pid, &exit_status, 0);
|
||||
if (WIFEXITED(exit_status))
|
||||
goto done;
|
||||
die("Subprocess %s exited abnormally", filter->cmd);
|
||||
|
||||
done:
|
||||
for (i = 0; i < filter->base.argument_count; i++)
|
||||
filter->argv[i + 1] = NULL;
|
||||
return WEXITSTATUS(exit_status);
|
||||
|
||||
}
|
||||
|
||||
static void fprintf_exec_filter(struct cgit_filter *base, FILE *f, const char *prefix)
|
||||
{
|
||||
struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
|
||||
fprintf(f, "%sexec:%s\n", prefix, filter->cmd);
|
||||
}
|
||||
|
||||
static void cleanup_exec_filter(struct cgit_filter *base)
|
||||
{
|
||||
struct cgit_exec_filter *filter = (struct cgit_exec_filter *)base;
|
||||
if (filter->argv) {
|
||||
free(filter->argv);
|
||||
filter->argv = NULL;
|
||||
}
|
||||
if (filter->cmd) {
|
||||
free(filter->cmd);
|
||||
filter->cmd = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static struct cgit_filter *new_exec_filter(const char *cmd, int argument_count)
|
||||
{
|
||||
struct cgit_exec_filter *f;
|
||||
int args_size = 0;
|
||||
|
||||
f = xmalloc(sizeof(*f));
|
||||
/* We leave argv for now and assign it below. */
|
||||
cgit_exec_filter_init(f, xstrdup(cmd), NULL);
|
||||
f->base.argument_count = argument_count;
|
||||
args_size = (2 + argument_count) * sizeof(char *);
|
||||
f->argv = xmalloc(args_size);
|
||||
memset(f->argv, 0, args_size);
|
||||
f->argv[0] = f->cmd;
|
||||
return &f->base;
|
||||
}
|
||||
|
||||
void cgit_exec_filter_init(struct cgit_exec_filter *filter, char *cmd, char **argv)
|
||||
{
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
filter->base.open = open_exec_filter;
|
||||
filter->base.close = close_exec_filter;
|
||||
filter->base.fprintf = fprintf_exec_filter;
|
||||
filter->base.cleanup = cleanup_exec_filter;
|
||||
filter->cmd = cmd;
|
||||
filter->argv = argv;
|
||||
/* The argument count for open_filter is zero by default, unless called from new_filter, above. */
|
||||
filter->base.argument_count = 0;
|
||||
}
|
||||
|
||||
#ifdef NO_LUA
|
||||
void cgit_init_filters(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef NO_LUA
|
||||
static ssize_t (*libc_write)(int fd, const void *buf, size_t count);
|
||||
static ssize_t (*filter_write)(struct cgit_filter *base, const void *buf, size_t count) = NULL;
|
||||
static struct cgit_filter *current_write_filter = NULL;
|
||||
|
||||
void cgit_init_filters(void)
|
||||
{
|
||||
libc_write = dlsym(RTLD_NEXT, "write");
|
||||
if (!libc_write)
|
||||
die("Could not locate libc's write function");
|
||||
}
|
||||
|
||||
ssize_t write(int fd, const void *buf, size_t count)
|
||||
{
|
||||
if (fd != STDOUT_FILENO || !filter_write)
|
||||
return libc_write(fd, buf, count);
|
||||
return filter_write(current_write_filter, buf, count);
|
||||
}
|
||||
|
||||
static inline void hook_write(struct cgit_filter *filter, ssize_t (*new_write)(struct cgit_filter *base, const void *buf, size_t count))
|
||||
{
|
||||
/* We want to avoid buggy nested patterns. */
|
||||
assert(filter_write == NULL);
|
||||
assert(current_write_filter == NULL);
|
||||
current_write_filter = filter;
|
||||
filter_write = new_write;
|
||||
}
|
||||
|
||||
static inline void unhook_write(void)
|
||||
{
|
||||
assert(filter_write != NULL);
|
||||
assert(current_write_filter != NULL);
|
||||
filter_write = NULL;
|
||||
current_write_filter = NULL;
|
||||
}
|
||||
|
||||
struct lua_filter {
|
||||
struct cgit_filter base;
|
||||
char *script_file;
|
||||
lua_State *lua_state;
|
||||
};
|
||||
|
||||
static void error_lua_filter(struct lua_filter *filter)
|
||||
{
|
||||
die("Lua error in %s: %s", filter->script_file, lua_tostring(filter->lua_state, -1));
|
||||
lua_pop(filter->lua_state, 1);
|
||||
}
|
||||
|
||||
static ssize_t write_lua_filter(struct cgit_filter *base, const void *buf, size_t count)
|
||||
{
|
||||
struct lua_filter *filter = (struct lua_filter *)base;
|
||||
|
||||
lua_getglobal(filter->lua_state, "filter_write");
|
||||
lua_pushlstring(filter->lua_state, buf, count);
|
||||
if (lua_pcall(filter->lua_state, 1, 0, 0)) {
|
||||
error_lua_filter(filter);
|
||||
errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static inline int hook_lua_filter(lua_State *lua_state, void (*fn)(const char *txt))
|
||||
{
|
||||
const char *str;
|
||||
ssize_t (*save_filter_write)(struct cgit_filter *base, const void *buf, size_t count);
|
||||
struct cgit_filter *save_filter;
|
||||
|
||||
str = lua_tostring(lua_state, 1);
|
||||
if (!str)
|
||||
return 0;
|
||||
|
||||
save_filter_write = filter_write;
|
||||
save_filter = current_write_filter;
|
||||
unhook_write();
|
||||
fn(str);
|
||||
hook_write(save_filter, save_filter_write);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int html_lua_filter(lua_State *lua_state)
|
||||
{
|
||||
return hook_lua_filter(lua_state, html);
|
||||
}
|
||||
|
||||
static int html_txt_lua_filter(lua_State *lua_state)
|
||||
{
|
||||
return hook_lua_filter(lua_state, html_txt);
|
||||
}
|
||||
|
||||
static int html_attr_lua_filter(lua_State *lua_state)
|
||||
{
|
||||
return hook_lua_filter(lua_state, html_attr);
|
||||
}
|
||||
|
||||
static int html_url_path_lua_filter(lua_State *lua_state)
|
||||
{
|
||||
return hook_lua_filter(lua_state, html_url_path);
|
||||
}
|
||||
|
||||
static int html_url_arg_lua_filter(lua_State *lua_state)
|
||||
{
|
||||
return hook_lua_filter(lua_state, html_url_arg);
|
||||
}
|
||||
|
||||
static int html_include_lua_filter(lua_State *lua_state)
|
||||
{
|
||||
return hook_lua_filter(lua_state, (void (*)(const char *))html_include);
|
||||
}
|
||||
|
||||
static void cleanup_lua_filter(struct cgit_filter *base)
|
||||
{
|
||||
struct lua_filter *filter = (struct lua_filter *)base;
|
||||
|
||||
if (!filter->lua_state)
|
||||
return;
|
||||
|
||||
lua_close(filter->lua_state);
|
||||
filter->lua_state = NULL;
|
||||
if (filter->script_file) {
|
||||
free(filter->script_file);
|
||||
filter->script_file = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int init_lua_filter(struct lua_filter *filter)
|
||||
{
|
||||
if (filter->lua_state)
|
||||
return 0;
|
||||
|
||||
if (!(filter->lua_state = luaL_newstate()))
|
||||
return 1;
|
||||
|
||||
luaL_openlibs(filter->lua_state);
|
||||
|
||||
lua_pushcfunction(filter->lua_state, html_lua_filter);
|
||||
lua_setglobal(filter->lua_state, "html");
|
||||
lua_pushcfunction(filter->lua_state, html_txt_lua_filter);
|
||||
lua_setglobal(filter->lua_state, "html_txt");
|
||||
lua_pushcfunction(filter->lua_state, html_attr_lua_filter);
|
||||
lua_setglobal(filter->lua_state, "html_attr");
|
||||
lua_pushcfunction(filter->lua_state, html_url_path_lua_filter);
|
||||
lua_setglobal(filter->lua_state, "html_url_path");
|
||||
lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
|
||||
lua_setglobal(filter->lua_state, "html_url_arg");
|
||||
lua_pushcfunction(filter->lua_state, html_include_lua_filter);
|
||||
lua_setglobal(filter->lua_state, "html_include");
|
||||
|
||||
if (luaL_dofile(filter->lua_state, filter->script_file)) {
|
||||
error_lua_filter(filter);
|
||||
lua_close(filter->lua_state);
|
||||
filter->lua_state = NULL;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int open_lua_filter(struct cgit_filter *base, va_list ap)
|
||||
{
|
||||
struct lua_filter *filter = (struct lua_filter *)base;
|
||||
int i;
|
||||
|
||||
if (init_lua_filter(filter))
|
||||
return 1;
|
||||
|
||||
hook_write(base, write_lua_filter);
|
||||
|
||||
lua_getglobal(filter->lua_state, "filter_open");
|
||||
for (i = 0; i < filter->base.argument_count; ++i)
|
||||
lua_pushstring(filter->lua_state, va_arg(ap, char *));
|
||||
if (lua_pcall(filter->lua_state, filter->base.argument_count, 0, 0)) {
|
||||
error_lua_filter(filter);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int close_lua_filter(struct cgit_filter *base)
|
||||
{
|
||||
struct lua_filter *filter = (struct lua_filter *)base;
|
||||
int ret = 0;
|
||||
|
||||
lua_getglobal(filter->lua_state, "filter_close");
|
||||
if (lua_pcall(filter->lua_state, 0, 1, 0)) {
|
||||
error_lua_filter(filter);
|
||||
ret = -1;
|
||||
} else {
|
||||
ret = lua_tonumber(filter->lua_state, -1);
|
||||
lua_pop(filter->lua_state, 1);
|
||||
}
|
||||
|
||||
unhook_write();
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fprintf_lua_filter(struct cgit_filter *base, FILE *f, const char *prefix)
|
||||
{
|
||||
struct lua_filter *filter = (struct lua_filter *)base;
|
||||
fprintf(f, "%slua:%s\n", prefix, filter->script_file);
|
||||
}
|
||||
|
||||
|
||||
static struct cgit_filter *new_lua_filter(const char *cmd, int argument_count)
|
||||
{
|
||||
struct lua_filter *filter;
|
||||
|
||||
filter = xmalloc(sizeof(*filter));
|
||||
memset(filter, 0, sizeof(*filter));
|
||||
filter->base.open = open_lua_filter;
|
||||
filter->base.close = close_lua_filter;
|
||||
filter->base.fprintf = fprintf_lua_filter;
|
||||
filter->base.cleanup = cleanup_lua_filter;
|
||||
filter->base.argument_count = argument_count;
|
||||
filter->script_file = xstrdup(cmd);
|
||||
|
||||
return &filter->base;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
int cgit_open_filter(struct cgit_filter *filter, ...)
|
||||
{
|
||||
int result;
|
||||
va_list ap;
|
||||
if (!filter)
|
||||
return 0;
|
||||
va_start(ap, filter);
|
||||
result = filter->open(filter, ap);
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
int cgit_close_filter(struct cgit_filter *filter)
|
||||
{
|
||||
if (!filter)
|
||||
return 0;
|
||||
return filter->close(filter);
|
||||
}
|
||||
|
||||
void cgit_fprintf_filter(struct cgit_filter *filter, FILE *f, const char *prefix)
|
||||
{
|
||||
filter->fprintf(filter, f, prefix);
|
||||
}
|
||||
|
||||
|
||||
|
||||
static const struct {
|
||||
const char *prefix;
|
||||
struct cgit_filter *(*ctor)(const char *cmd, int argument_count);
|
||||
} filter_specs[] = {
|
||||
{ "exec", new_exec_filter },
|
||||
#ifndef NO_LUA
|
||||
{ "lua", new_lua_filter },
|
||||
#endif
|
||||
};
|
||||
|
||||
struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
|
||||
{
|
||||
char *colon;
|
||||
int i;
|
||||
size_t len;
|
||||
int argument_count;
|
||||
|
||||
if (!cmd || !cmd[0])
|
||||
return NULL;
|
||||
|
||||
colon = strchr(cmd, ':');
|
||||
len = colon - cmd;
|
||||
/*
|
||||
* In case we're running on Windows, don't allow a single letter before
|
||||
* the colon.
|
||||
*/
|
||||
if (len == 1)
|
||||
colon = NULL;
|
||||
|
||||
switch (filtertype) {
|
||||
case AUTH:
|
||||
argument_count = 12;
|
||||
break;
|
||||
|
||||
case EMAIL:
|
||||
argument_count = 2;
|
||||
break;
|
||||
|
||||
case OWNER:
|
||||
argument_count = 0;
|
||||
break;
|
||||
|
||||
case SOURCE:
|
||||
case ABOUT:
|
||||
argument_count = 1;
|
||||
break;
|
||||
|
||||
case COMMIT:
|
||||
default:
|
||||
argument_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
/* If no prefix is given, exec filter is the default. */
|
||||
if (!colon)
|
||||
return new_exec_filter(cmd, argument_count);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(filter_specs); i++) {
|
||||
if (len == strlen(filter_specs[i].prefix) &&
|
||||
!strncmp(filter_specs[i].prefix, cmd, len))
|
||||
return filter_specs[i].ctor(colon + 1, argument_count);
|
||||
}
|
||||
|
||||
die("Invalid filter type: %.*s", (int) len, cmd);
|
||||
}
|
27
filters/about-formatting.sh
Executable file
27
filters/about-formatting.sh
Executable file
|
@ -0,0 +1,27 @@
|
|||
#!/bin/sh
|
||||
|
||||
# This may be used with the about-filter or repo.about-filter setting in cgitrc.
|
||||
# It passes formatting of about pages to differing programs, depending on the usage.
|
||||
|
||||
# Markdown support requires python and markdown-python.
|
||||
# RestructuredText support requires python and docutils.
|
||||
# Man page support requires groff.
|
||||
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
|
||||
cd "$(dirname $0)/html-converters/"
|
||||
case "$(printf '%s' "$1" | tr '[:upper:]' '[:lower:]')" in
|
||||
*.markdown|*.mdown|*.md|*.mkd) exec ./md2html; ;;
|
||||
*.rst) exec ./rst2html; ;;
|
||||
*.[1-9]) exec ./man2html; ;;
|
||||
*.htm|*.html) exec cat; ;;
|
||||
*.txt|*) exec ./txt2html; ;;
|
||||
esac
|
28
filters/commit-links.sh
Executable file
28
filters/commit-links.sh
Executable file
|
@ -0,0 +1,28 @@
|
|||
#!/bin/sh
|
||||
# This script can be used to generate links in commit messages.
|
||||
#
|
||||
# To use this script, refer to this file with either the commit-filter or the
|
||||
# repo.commit-filter options in cgitrc.
|
||||
#
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
#
|
||||
|
||||
regex=''
|
||||
|
||||
# This expression generates links to commits referenced by their SHA1.
|
||||
regex=$regex'
|
||||
s|\b([0-9a-fA-F]{7,40})\b|<a href="./?id=\1">\1</a>|g'
|
||||
|
||||
# This expression generates links to a fictional bugtracker.
|
||||
regex=$regex'
|
||||
s|#([0-9]+)\b|<a href="http://bugs.example.com/?bug=\1">#\1</a>|g'
|
||||
|
||||
sed -re "$regex"
|
35
filters/email-gravatar.lua
Normal file
35
filters/email-gravatar.lua
Normal file
|
@ -0,0 +1,35 @@
|
|||
-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
|
||||
-- It adds gravatar icons to author names. It is designed to be used with the lua:
|
||||
-- prefix in filters. It is much faster than the corresponding python script.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
--
|
||||
|
||||
local digest = require("openssl.digest")
|
||||
|
||||
function md5_hex(input)
|
||||
local b = digest.new("md5"):final(input)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
function filter_open(email, page)
|
||||
buffer = ""
|
||||
md5 = md5_hex(email:sub(2, -2):lower())
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
html("<img src='//www.gravatar.com/avatar/" .. md5 .. "?s=13&d=retro' width='13' height='13' alt='Gravatar' /> " .. buffer)
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. str
|
||||
end
|
||||
|
||||
|
39
filters/email-gravatar.py
Executable file
39
filters/email-gravatar.py
Executable file
|
@ -0,0 +1,39 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Please prefer the email-gravatar.lua using lua: as a prefix over this script. This
|
||||
# script is very slow, in comparison.
|
||||
#
|
||||
# This script may be used with the email-filter or repo.email-filter settings in cgitrc.
|
||||
#
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
#
|
||||
# It receives an email address on argv[1] and text on stdin. It prints
|
||||
# to stdout that text prepended by a gravatar at 10pt.
|
||||
|
||||
import sys
|
||||
import hashlib
|
||||
import codecs
|
||||
|
||||
email = sys.argv[1].lower().strip()
|
||||
if email[0] == '<':
|
||||
email = email[1:]
|
||||
if email[-1] == '>':
|
||||
email = email[0:-1]
|
||||
|
||||
page = sys.argv[2]
|
||||
|
||||
sys.stdin = codecs.getreader("utf-8")(sys.stdin.detach())
|
||||
sys.stdout = codecs.getwriter("utf-8")(sys.stdout.detach())
|
||||
|
||||
md5 = hashlib.md5(email.encode()).hexdigest()
|
||||
text = sys.stdin.read().strip()
|
||||
|
||||
print("<img src='//www.gravatar.com/avatar/" + md5 + "?s=13&d=retro' width='13' height='13' alt='Gravatar' /> " + text)
|
36
filters/email-libravatar.lua
Normal file
36
filters/email-libravatar.lua
Normal file
|
@ -0,0 +1,36 @@
|
|||
-- This script may be used with the email-filter or repo.email-filter settings in cgitrc.
|
||||
-- It adds libravatar icons to author names. It is designed to be used with the lua:
|
||||
-- prefix in filters.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
--
|
||||
|
||||
local digest = require("openssl.digest")
|
||||
|
||||
function md5_hex(input)
|
||||
local b = digest.new("md5"):final(input)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
function filter_open(email, page)
|
||||
buffer = ""
|
||||
md5 = md5_hex(email:sub(2, -2):lower())
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/"
|
||||
html("<img src='" .. baseurl .. "avatar/" .. md5 .. "?s=13&d=retro' width='13' height='13' alt='Libravatar' /> " .. buffer)
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. str
|
||||
end
|
||||
|
||||
|
359
filters/file-authentication.lua
Normal file
359
filters/file-authentication.lua
Normal file
|
@ -0,0 +1,359 @@
|
|||
-- This script may be used with the auth-filter.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
-- luaposix
|
||||
-- <https://github.com/luaposix/luaposix>
|
||||
--
|
||||
local sysstat = require("posix.sys.stat")
|
||||
local unistd = require("posix.unistd")
|
||||
local rand = require("openssl.rand")
|
||||
local hmac = require("openssl.hmac")
|
||||
|
||||
-- This file should contain a series of lines in the form of:
|
||||
-- username1:hash1
|
||||
-- username2:hash2
|
||||
-- username3:hash3
|
||||
-- ...
|
||||
-- Hashes can be generated using something like `mkpasswd -m sha-512 -R 300000`.
|
||||
-- This file should not be world-readable.
|
||||
local users_filename = "/etc/cgit-auth/users"
|
||||
|
||||
-- This file should contain a series of lines in the form of:
|
||||
-- groupname1:username1,username2,username3,...
|
||||
-- ...
|
||||
local groups_filename = "/etc/cgit-auth/groups"
|
||||
|
||||
-- This file should contain a series of lines in the form of:
|
||||
-- reponame1:groupname1,groupname2,groupname3,...
|
||||
-- ...
|
||||
local repos_filename = "/etc/cgit-auth/repos"
|
||||
|
||||
-- Set this to a path this script can write to for storing a persistent
|
||||
-- cookie secret, which should not be world-readable.
|
||||
local secret_filename = "/var/cache/cgit/auth-secret"
|
||||
|
||||
--
|
||||
--
|
||||
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
|
||||
--
|
||||
--
|
||||
|
||||
-- Looks up a hash for a given user.
|
||||
function lookup_hash(user)
|
||||
local line
|
||||
for line in io.lines(users_filename) do
|
||||
local u, h = string.match(line, "(.-):(.+)")
|
||||
if u:lower() == user:lower() then
|
||||
return h
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Looks up users for a given repo.
|
||||
function lookup_users(repo)
|
||||
local users = nil
|
||||
local groups = nil
|
||||
local line, group, user
|
||||
for line in io.lines(repos_filename) do
|
||||
local r, g = string.match(line, "(.-):(.+)")
|
||||
if r == repo then
|
||||
groups = { }
|
||||
for group in string.gmatch(g, "([^,]+)") do
|
||||
groups[group:lower()] = true
|
||||
end
|
||||
break
|
||||
end
|
||||
end
|
||||
if groups == nil then
|
||||
return nil
|
||||
end
|
||||
for line in io.lines(groups_filename) do
|
||||
local g, u = string.match(line, "(.-):(.+)")
|
||||
if groups[g:lower()] then
|
||||
if users == nil then
|
||||
users = { }
|
||||
end
|
||||
for user in string.gmatch(u, "([^,]+)") do
|
||||
users[user:lower()] = true
|
||||
end
|
||||
end
|
||||
end
|
||||
return users
|
||||
end
|
||||
|
||||
|
||||
-- Sets HTTP cookie headers based on post and sets up redirection.
|
||||
function authenticate_post()
|
||||
local hash = lookup_hash(post["username"])
|
||||
local redirect = validate_value("redirect", post["redirect"])
|
||||
|
||||
if redirect == nil then
|
||||
not_found()
|
||||
return 0
|
||||
end
|
||||
|
||||
redirect_to(redirect)
|
||||
|
||||
if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
|
||||
set_cookie("cgitauth", "")
|
||||
else
|
||||
-- One week expiration time
|
||||
local username = secure_value("username", post["username"], os.time() + 604800)
|
||||
set_cookie("cgitauth", username)
|
||||
end
|
||||
|
||||
html("\n")
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
-- Returns 1 if the cookie is valid and 0 if it is not.
|
||||
function authenticate_cookie()
|
||||
accepted_users = lookup_users(cgit["repo"])
|
||||
if accepted_users == nil then
|
||||
-- We return as valid if the repo is not protected.
|
||||
return 1
|
||||
end
|
||||
|
||||
local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
|
||||
if username == nil or not accepted_users[username:lower()] then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Prints the html for the login form.
|
||||
function body()
|
||||
html("<h2>Authentication Required</h2>")
|
||||
html("<form method='post' action='")
|
||||
html_attr(cgit["login"])
|
||||
html("'>")
|
||||
html("<input type='hidden' name='redirect' value='")
|
||||
html_attr(secure_value("redirect", cgit["url"], 0))
|
||||
html("' />")
|
||||
html("<table>")
|
||||
html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
|
||||
html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
|
||||
html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
|
||||
html("</table></form>")
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
|
||||
--
|
||||
--
|
||||
|
||||
local actions = {}
|
||||
actions["authenticate-post"] = authenticate_post
|
||||
actions["authenticate-cookie"] = authenticate_cookie
|
||||
actions["body"] = body
|
||||
|
||||
function filter_open(...)
|
||||
action = actions[select(1, ...)]
|
||||
|
||||
http = {}
|
||||
http["cookie"] = select(2, ...)
|
||||
http["method"] = select(3, ...)
|
||||
http["query"] = select(4, ...)
|
||||
http["referer"] = select(5, ...)
|
||||
http["path"] = select(6, ...)
|
||||
http["host"] = select(7, ...)
|
||||
http["https"] = select(8, ...)
|
||||
|
||||
cgit = {}
|
||||
cgit["repo"] = select(9, ...)
|
||||
cgit["page"] = select(10, ...)
|
||||
cgit["url"] = select(11, ...)
|
||||
cgit["login"] = select(12, ...)
|
||||
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
return action()
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
post = parse_qs(str)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Utility functions based on keplerproject/wsapi.
|
||||
--
|
||||
--
|
||||
|
||||
function url_decode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "+", " ")
|
||||
str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
|
||||
str = string.gsub(str, "\r\n", "\n")
|
||||
return str
|
||||
end
|
||||
|
||||
function url_encode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
return str
|
||||
end
|
||||
|
||||
function parse_qs(qs)
|
||||
local tab = {}
|
||||
for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
|
||||
tab[url_decode(key)] = url_decode(val)
|
||||
end
|
||||
return tab
|
||||
end
|
||||
|
||||
function get_cookie(cookies, name)
|
||||
cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
|
||||
return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
|
||||
end
|
||||
|
||||
function tohex(b)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Cookie construction and validation helpers.
|
||||
--
|
||||
--
|
||||
|
||||
local secret = nil
|
||||
|
||||
-- Loads a secret from a file, creates a secret, or returns one from memory.
|
||||
function get_secret()
|
||||
if secret ~= nil then
|
||||
return secret
|
||||
end
|
||||
local secret_file = io.open(secret_filename, "r")
|
||||
if secret_file == nil then
|
||||
local old_umask = sysstat.umask(63)
|
||||
local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
|
||||
local temporary_file = io.open(temporary_filename, "w")
|
||||
if temporary_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
temporary_file:write(tohex(rand.bytes(32)))
|
||||
temporary_file:close()
|
||||
unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
|
||||
unistd.unlink(temporary_filename)
|
||||
sysstat.umask(old_umask)
|
||||
secret_file = io.open(secret_filename, "r")
|
||||
end
|
||||
if secret_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
secret = secret_file:read()
|
||||
secret_file:close()
|
||||
if secret:len() ~= 64 then
|
||||
os.exit(177)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
-- Returns value of cookie if cookie is valid. Otherwise returns nil.
|
||||
function validate_value(expected_field, cookie)
|
||||
local i = 0
|
||||
local value = ""
|
||||
local field = ""
|
||||
local expiration = 0
|
||||
local salt = ""
|
||||
local chmac = ""
|
||||
|
||||
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for component in string.gmatch(cookie, "[^|]+") do
|
||||
if i == 0 then
|
||||
field = component
|
||||
elseif i == 1 then
|
||||
value = component
|
||||
elseif i == 2 then
|
||||
expiration = tonumber(component)
|
||||
if expiration == nil then
|
||||
expiration = -1
|
||||
end
|
||||
elseif i == 3 then
|
||||
salt = component
|
||||
elseif i == 4 then
|
||||
chmac = component
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if chmac == nil or chmac:len() == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Lua hashes strings, so these comparisons are time invariant.
|
||||
if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if url_decode(field) ~= expected_field then
|
||||
return nil
|
||||
end
|
||||
|
||||
return url_decode(value)
|
||||
end
|
||||
|
||||
function secure_value(field, value, expiration)
|
||||
if value == nil or value:len() <= 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local authstr = ""
|
||||
local salt = tohex(rand.bytes(16))
|
||||
value = url_encode(value)
|
||||
field = url_encode(field)
|
||||
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
|
||||
authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
|
||||
return authstr
|
||||
end
|
||||
|
||||
function set_cookie(cookie, value)
|
||||
html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
|
||||
if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
|
||||
html("; secure")
|
||||
end
|
||||
html("\n")
|
||||
end
|
||||
|
||||
function redirect_to(url)
|
||||
html("Status: 302 Redirect\n")
|
||||
html("Cache-Control: no-cache, no-store\n")
|
||||
html("Location: " .. url .. "\n")
|
||||
end
|
||||
|
||||
function not_found()
|
||||
html("Status: 404 Not Found\n")
|
||||
html("Cache-Control: no-cache, no-store\n\n")
|
||||
end
|
360
filters/gentoo-ldap-authentication.lua
Normal file
360
filters/gentoo-ldap-authentication.lua
Normal file
|
@ -0,0 +1,360 @@
|
|||
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
-- lualdap >= 1.2
|
||||
-- <https://git.zx2c4.com/lualdap/about/>
|
||||
-- luaposix
|
||||
-- <https://github.com/luaposix/luaposix>
|
||||
--
|
||||
local sysstat = require("posix.sys.stat")
|
||||
local unistd = require("posix.unistd")
|
||||
local lualdap = require("lualdap")
|
||||
local rand = require("openssl.rand")
|
||||
local hmac = require("openssl.hmac")
|
||||
|
||||
--
|
||||
--
|
||||
-- Configure these variables for your settings.
|
||||
--
|
||||
--
|
||||
|
||||
-- A list of password protected repositories, with which gentooAccess
|
||||
-- group is allowed to access each one.
|
||||
local protected_repos = {
|
||||
glouglou = "infra",
|
||||
portage = "dev"
|
||||
}
|
||||
|
||||
-- Set this to a path this script can write to for storing a persistent
|
||||
-- cookie secret, which should be guarded.
|
||||
local secret_filename = "/var/cache/cgit/auth-secret"
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
|
||||
--
|
||||
--
|
||||
|
||||
-- Sets HTTP cookie headers based on post and sets up redirection.
|
||||
function authenticate_post()
|
||||
local redirect = validate_value("redirect", post["redirect"])
|
||||
|
||||
if redirect == nil then
|
||||
not_found()
|
||||
return 0
|
||||
end
|
||||
|
||||
redirect_to(redirect)
|
||||
|
||||
local groups = gentoo_ldap_user_groups(post["username"], post["password"])
|
||||
if groups == nil then
|
||||
set_cookie("cgitauth", "")
|
||||
else
|
||||
-- One week expiration time
|
||||
set_cookie("cgitauth", secure_value("gentoogroups", table.concat(groups, ","), os.time() + 604800))
|
||||
end
|
||||
|
||||
html("\n")
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
-- Returns 1 if the cookie is valid and 0 if it is not.
|
||||
function authenticate_cookie()
|
||||
local required_group = protected_repos[cgit["repo"]]
|
||||
if required_group == nil then
|
||||
-- We return as valid if the repo is not protected.
|
||||
return 1
|
||||
end
|
||||
|
||||
local user_groups = validate_value("gentoogroups", get_cookie(http["cookie"], "cgitauth"))
|
||||
if user_groups == nil or user_groups == "" then
|
||||
return 0
|
||||
end
|
||||
for group in string.gmatch(user_groups, "[^,]+") do
|
||||
if group == required_group then
|
||||
return 1
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
-- Prints the html for the login form.
|
||||
function body()
|
||||
html("<h2>Gentoo LDAP Authentication Required</h2>")
|
||||
html("<form method='post' action='")
|
||||
html_attr(cgit["login"])
|
||||
html("'>")
|
||||
html("<input type='hidden' name='redirect' value='")
|
||||
html_attr(secure_value("redirect", cgit["url"], 0))
|
||||
html("' />")
|
||||
html("<table>")
|
||||
html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
|
||||
html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
|
||||
html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
|
||||
html("</table></form>")
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Gentoo LDAP support.
|
||||
--
|
||||
--
|
||||
|
||||
function gentoo_ldap_user_groups(username, password)
|
||||
-- Ensure the user is alphanumeric
|
||||
if username == nil or username:match("%W") then
|
||||
return nil
|
||||
end
|
||||
|
||||
local who = "uid=" .. username .. ",ou=devs,dc=gentoo,dc=org"
|
||||
|
||||
local ldap, err = lualdap.open_simple {
|
||||
uri = "ldap://ldap1.gentoo.org",
|
||||
who = who,
|
||||
password = password,
|
||||
starttls = true,
|
||||
certfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.crt",
|
||||
keyfile = "/var/www/uwsgi/cgit/gentoo-ldap/star.gentoo.org.key",
|
||||
cacertfile = "/var/www/uwsgi/cgit/gentoo-ldap/ca.pem"
|
||||
}
|
||||
if ldap == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
local group_suffix = ".group"
|
||||
local group_suffix_len = group_suffix:len()
|
||||
local groups = {}
|
||||
for dn, attribs in ldap:search { base = who, scope = "subtree" } do
|
||||
local access = attribs["gentooAccess"]
|
||||
if dn == who and access ~= nil then
|
||||
for i, v in ipairs(access) do
|
||||
local vlen = v:len()
|
||||
if vlen > group_suffix_len and v:sub(-group_suffix_len) == group_suffix then
|
||||
table.insert(groups, v:sub(1, vlen - group_suffix_len))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ldap:close()
|
||||
|
||||
return groups
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
|
||||
--
|
||||
--
|
||||
|
||||
local actions = {}
|
||||
actions["authenticate-post"] = authenticate_post
|
||||
actions["authenticate-cookie"] = authenticate_cookie
|
||||
actions["body"] = body
|
||||
|
||||
function filter_open(...)
|
||||
action = actions[select(1, ...)]
|
||||
|
||||
http = {}
|
||||
http["cookie"] = select(2, ...)
|
||||
http["method"] = select(3, ...)
|
||||
http["query"] = select(4, ...)
|
||||
http["referer"] = select(5, ...)
|
||||
http["path"] = select(6, ...)
|
||||
http["host"] = select(7, ...)
|
||||
http["https"] = select(8, ...)
|
||||
|
||||
cgit = {}
|
||||
cgit["repo"] = select(9, ...)
|
||||
cgit["page"] = select(10, ...)
|
||||
cgit["url"] = select(11, ...)
|
||||
cgit["login"] = select(12, ...)
|
||||
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
return action()
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
post = parse_qs(str)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Utility functions based on keplerproject/wsapi.
|
||||
--
|
||||
--
|
||||
|
||||
function url_decode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "+", " ")
|
||||
str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
|
||||
str = string.gsub(str, "\r\n", "\n")
|
||||
return str
|
||||
end
|
||||
|
||||
function url_encode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
return str
|
||||
end
|
||||
|
||||
function parse_qs(qs)
|
||||
local tab = {}
|
||||
for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
|
||||
tab[url_decode(key)] = url_decode(val)
|
||||
end
|
||||
return tab
|
||||
end
|
||||
|
||||
function get_cookie(cookies, name)
|
||||
cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
|
||||
return string.match(cookies, ";" .. name .. "=(.-);")
|
||||
end
|
||||
|
||||
function tohex(b)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Cookie construction and validation helpers.
|
||||
--
|
||||
--
|
||||
|
||||
local secret = nil
|
||||
|
||||
-- Loads a secret from a file, creates a secret, or returns one from memory.
|
||||
function get_secret()
|
||||
if secret ~= nil then
|
||||
return secret
|
||||
end
|
||||
local secret_file = io.open(secret_filename, "r")
|
||||
if secret_file == nil then
|
||||
local old_umask = sysstat.umask(63)
|
||||
local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
|
||||
local temporary_file = io.open(temporary_filename, "w")
|
||||
if temporary_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
temporary_file:write(tohex(rand.bytes(32)))
|
||||
temporary_file:close()
|
||||
unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
|
||||
unistd.unlink(temporary_filename)
|
||||
sysstat.umask(old_umask)
|
||||
secret_file = io.open(secret_filename, "r")
|
||||
end
|
||||
if secret_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
secret = secret_file:read()
|
||||
secret_file:close()
|
||||
if secret:len() ~= 64 then
|
||||
os.exit(177)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
-- Returns value of cookie if cookie is valid. Otherwise returns nil.
|
||||
function validate_value(expected_field, cookie)
|
||||
local i = 0
|
||||
local value = ""
|
||||
local field = ""
|
||||
local expiration = 0
|
||||
local salt = ""
|
||||
local chmac = ""
|
||||
|
||||
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for component in string.gmatch(cookie, "[^|]+") do
|
||||
if i == 0 then
|
||||
field = component
|
||||
elseif i == 1 then
|
||||
value = component
|
||||
elseif i == 2 then
|
||||
expiration = tonumber(component)
|
||||
if expiration == nil then
|
||||
expiration = -1
|
||||
end
|
||||
elseif i == 3 then
|
||||
salt = component
|
||||
elseif i == 4 then
|
||||
chmac = component
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if chmac == nil or chmac:len() == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Lua hashes strings, so these comparisons are time invariant.
|
||||
if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if url_decode(field) ~= expected_field then
|
||||
return nil
|
||||
end
|
||||
|
||||
return url_decode(value)
|
||||
end
|
||||
|
||||
function secure_value(field, value, expiration)
|
||||
if value == nil or value:len() <= 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local authstr = ""
|
||||
local salt = tohex(rand.bytes(16))
|
||||
value = url_encode(value)
|
||||
field = url_encode(field)
|
||||
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
|
||||
authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
|
||||
return authstr
|
||||
end
|
||||
|
||||
function set_cookie(cookie, value)
|
||||
html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
|
||||
if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
|
||||
html("; secure")
|
||||
end
|
||||
html("\n")
|
||||
end
|
||||
|
||||
function redirect_to(url)
|
||||
html("Status: 302 Redirect\n")
|
||||
html("Cache-Control: no-cache, no-store\n")
|
||||
html("Location: " .. url .. "\n")
|
||||
end
|
||||
|
||||
function not_found()
|
||||
html("Status: 404 Not Found\n")
|
||||
html("Cache-Control: no-cache, no-store\n\n")
|
||||
end
|
4
filters/html-converters/man2html
Executable file
4
filters/html-converters/man2html
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
echo "<div style=\"font-family: monospace\">"
|
||||
groff -mandoc -T html -P -r -P -l | egrep -v '(<html>|<head>|<meta|<title>|</title>|</head>|<body>|</body>|</html>|<!DOCTYPE|"http://www.w3.org)'
|
||||
echo "</div>"
|
307
filters/html-converters/md2html
Executable file
307
filters/html-converters/md2html
Executable file
|
@ -0,0 +1,307 @@
|
|||
#!/usr/bin/env python3
|
||||
import markdown
|
||||
import sys
|
||||
import io
|
||||
from pygments.formatters import HtmlFormatter
|
||||
from markdown.extensions.toc import TocExtension
|
||||
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
||||
sys.stdout.write('''
|
||||
<style>
|
||||
.markdown-body {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
overflow: hidden;
|
||||
}
|
||||
.markdown-body>*:first-child {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
.markdown-body>*:last-child {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
.markdown-body a.absent {
|
||||
color: #c00;
|
||||
}
|
||||
.markdown-body a.anchor {
|
||||
display: block;
|
||||
padding-left: 30px;
|
||||
margin-left: -30px;
|
||||
cursor: pointer;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3, .markdown-body h4, .markdown-body h5, .markdown-body h6 {
|
||||
margin: 20px 0 10px;
|
||||
padding: 0;
|
||||
font-weight: bold;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
cursor: text;
|
||||
position: relative;
|
||||
}
|
||||
.markdown-body h1 .mini-icon-link, .markdown-body h2 .mini-icon-link, .markdown-body h3 .mini-icon-link, .markdown-body h4 .mini-icon-link, .markdown-body h5 .mini-icon-link, .markdown-body h6 .mini-icon-link {
|
||||
display: none;
|
||||
color: #000;
|
||||
}
|
||||
.markdown-body h1:hover a.anchor, .markdown-body h2:hover a.anchor, .markdown-body h3:hover a.anchor, .markdown-body h4:hover a.anchor, .markdown-body h5:hover a.anchor, .markdown-body h6:hover a.anchor {
|
||||
text-decoration: none;
|
||||
line-height: 1;
|
||||
padding-left: 0;
|
||||
margin-left: -22px;
|
||||
top: 15%;
|
||||
}
|
||||
.markdown-body h1:hover a.anchor .mini-icon-link, .markdown-body h2:hover a.anchor .mini-icon-link, .markdown-body h3:hover a.anchor .mini-icon-link, .markdown-body h4:hover a.anchor .mini-icon-link, .markdown-body h5:hover a.anchor .mini-icon-link, .markdown-body h6:hover a.anchor .mini-icon-link {
|
||||
display: inline-block;
|
||||
}
|
||||
div#cgit .markdown-body h1 a.toclink, div#cgit .markdown-body h2 a.toclink, div#cgit .markdown-body h3 a.toclink, div#cgit .markdown-body h4 a.toclink, div#cgit .markdown-body h5 a.toclink, div#cgit .markdown-body h6 a.toclink {
|
||||
color: black;
|
||||
}
|
||||
.markdown-body h1 tt, .markdown-body h1 code, .markdown-body h2 tt, .markdown-body h2 code, .markdown-body h3 tt, .markdown-body h3 code, .markdown-body h4 tt, .markdown-body h4 code, .markdown-body h5 tt, .markdown-body h5 code, .markdown-body h6 tt, .markdown-body h6 code {
|
||||
font-size: inherit;
|
||||
}
|
||||
.markdown-body h1 {
|
||||
font-size: 28px;
|
||||
color: #000;
|
||||
}
|
||||
.markdown-body h2 {
|
||||
font-size: 24px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
color: #000;
|
||||
}
|
||||
.markdown-body h3 {
|
||||
font-size: 18px;
|
||||
}
|
||||
.markdown-body h4 {
|
||||
font-size: 16px;
|
||||
}
|
||||
.markdown-body h5 {
|
||||
font-size: 14px;
|
||||
}
|
||||
.markdown-body h6 {
|
||||
color: #777;
|
||||
font-size: 14px;
|
||||
}
|
||||
.markdown-body p, .markdown-body blockquote, .markdown-body ul, .markdown-body ol, .markdown-body dl, .markdown-body table, .markdown-body pre {
|
||||
margin: 15px 0;
|
||||
}
|
||||
.markdown-body hr {
|
||||
background: transparent url("/dirty-shade.png") repeat-x 0 0;
|
||||
border: 0 none;
|
||||
color: #ccc;
|
||||
height: 4px;
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body>h2:first-child, .markdown-body>h1:first-child, .markdown-body>h1:first-child+h2, .markdown-body>h3:first-child, .markdown-body>h4:first-child, .markdown-body>h5:first-child, .markdown-body>h6:first-child {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.markdown-body a:first-child h1, .markdown-body a:first-child h2, .markdown-body a:first-child h3, .markdown-body a:first-child h4, .markdown-body a:first-child h5, .markdown-body a:first-child h6 {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
.markdown-body h1+p, .markdown-body h2+p, .markdown-body h3+p, .markdown-body h4+p, .markdown-body h5+p, .markdown-body h6+p {
|
||||
margin-top: 0;
|
||||
}
|
||||
.markdown-body li p.first {
|
||||
display: inline-block;
|
||||
}
|
||||
.markdown-body ul, .markdown-body ol {
|
||||
padding-left: 30px;
|
||||
}
|
||||
.markdown-body ul.no-list, .markdown-body ol.no-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body ul li>:first-child, .markdown-body ul li ul:first-of-type, .markdown-body ul li ol:first-of-type, .markdown-body ol li>:first-child, .markdown-body ol li ul:first-of-type, .markdown-body ol li ol:first-of-type {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body ul li p:last-of-type, .markdown-body ol li p:last-of-type {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.markdown-body ul ul, .markdown-body ul ol, .markdown-body ol ol, .markdown-body ol ul {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.markdown-body dl {
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body dl dt {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
padding: 0;
|
||||
margin: 15px 0 5px;
|
||||
}
|
||||
.markdown-body dl dt:first-child {
|
||||
padding: 0;
|
||||
}
|
||||
.markdown-body dl dt>:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body dl dt>:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.markdown-body dl dd {
|
||||
margin: 0 0 15px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
.markdown-body dl dd>:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body dl dd>:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.markdown-body blockquote {
|
||||
border-left: 4px solid #DDD;
|
||||
padding: 0 15px;
|
||||
color: #777;
|
||||
}
|
||||
.markdown-body blockquote>:first-child {
|
||||
margin-top: 0px;
|
||||
}
|
||||
.markdown-body blockquote>:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
.markdown-body table th {
|
||||
font-weight: bold;
|
||||
}
|
||||
.markdown-body table th, .markdown-body table td {
|
||||
border: 1px solid #ccc;
|
||||
padding: 6px 13px;
|
||||
}
|
||||
.markdown-body table tr {
|
||||
border-top: 1px solid #ccc;
|
||||
background-color: #fff;
|
||||
}
|
||||
.markdown-body table tr:nth-child(2n) {
|
||||
background-color: #f8f8f8;
|
||||
}
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.markdown-body span.frame {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.markdown-body span.frame>span {
|
||||
border: 1px solid #ddd;
|
||||
display: block;
|
||||
float: left;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
padding: 7px;
|
||||
width: auto;
|
||||
}
|
||||
.markdown-body span.frame span img {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
.markdown-body span.frame span span {
|
||||
clear: both;
|
||||
color: #333;
|
||||
display: block;
|
||||
padding: 5px 0 0;
|
||||
}
|
||||
.markdown-body span.align-center {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
}
|
||||
.markdown-body span.align-center>span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: center;
|
||||
}
|
||||
.markdown-body span.align-center span img {
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
}
|
||||
.markdown-body span.align-right {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
}
|
||||
.markdown-body span.align-right>span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px 0 0;
|
||||
text-align: right;
|
||||
}
|
||||
.markdown-body span.align-right span img {
|
||||
margin: 0;
|
||||
text-align: right;
|
||||
}
|
||||
.markdown-body span.float-left {
|
||||
display: block;
|
||||
margin-right: 13px;
|
||||
overflow: hidden;
|
||||
float: left;
|
||||
}
|
||||
.markdown-body span.float-left span {
|
||||
margin: 13px 0 0;
|
||||
}
|
||||
.markdown-body span.float-right {
|
||||
display: block;
|
||||
margin-left: 13px;
|
||||
overflow: hidden;
|
||||
float: right;
|
||||
}
|
||||
.markdown-body span.float-right>span {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
margin: 13px auto 0;
|
||||
text-align: right;
|
||||
}
|
||||
.markdown-body code, .markdown-body tt {
|
||||
margin: 0 2px;
|
||||
padding: 0px 5px;
|
||||
border: 1px solid #eaeaea;
|
||||
background-color: #f8f8f8;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-body code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
.markdown-body pre>code {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
white-space: pre;
|
||||
border: none;
|
||||
background: transparent;
|
||||
}
|
||||
.markdown-body .highlight pre, .markdown-body pre {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ccc;
|
||||
font-size: 13px;
|
||||
line-height: 19px;
|
||||
overflow: auto;
|
||||
padding: 6px 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.markdown-body pre code, .markdown-body pre tt {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
''')
|
||||
sys.stdout.write(HtmlFormatter(style='pastie').get_style_defs('.highlight'))
|
||||
sys.stdout.write('''
|
||||
</style>
|
||||
''')
|
||||
sys.stdout.write("<div class='markdown-body'>")
|
||||
sys.stdout.flush()
|
||||
# Note: you may want to run this through bleach for sanitization
|
||||
markdown.markdownFromFile(
|
||||
output_format="html5",
|
||||
extensions=[
|
||||
"markdown.extensions.fenced_code",
|
||||
"markdown.extensions.codehilite",
|
||||
"markdown.extensions.tables",
|
||||
TocExtension(anchorlink=True)],
|
||||
extension_configs={
|
||||
"markdown.extensions.codehilite":{"css_class":"highlight"}})
|
||||
sys.stdout.write("</div>")
|
2
filters/html-converters/rst2html
Executable file
2
filters/html-converters/rst2html
Executable file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s")
|
4
filters/html-converters/txt2html
Executable file
4
filters/html-converters/txt2html
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
echo "<pre>"
|
||||
sed "s|&|\\&|g;s|'|\\'|g;s|\"|\\"|g;s|<|\\<|g;s|>|\\>|g"
|
||||
echo "</pre>"
|
17
filters/owner-example.lua
Normal file
17
filters/owner-example.lua
Normal file
|
@ -0,0 +1,17 @@
|
|||
-- This script is an example of an owner-filter. It replaces the
|
||||
-- usual query link with one to a fictional homepage. This script may
|
||||
-- be used with the owner-filter or repo.owner-filter settings in
|
||||
-- cgitrc with the `lua:` prefix.
|
||||
|
||||
function filter_open()
|
||||
buffer = ""
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
html(string.format("<a href=\"%s\">%s</a>", "http://wiki.example.com/about/" .. buffer, buffer))
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. str
|
||||
end
|
314
filters/simple-authentication.lua
Normal file
314
filters/simple-authentication.lua
Normal file
|
@ -0,0 +1,314 @@
|
|||
-- This script may be used with the auth-filter. Be sure to configure it as you wish.
|
||||
--
|
||||
-- Requirements:
|
||||
-- luaossl
|
||||
-- <http://25thandclement.com/~william/projects/luaossl.html>
|
||||
-- luaposix
|
||||
-- <https://github.com/luaposix/luaposix>
|
||||
--
|
||||
local sysstat = require("posix.sys.stat")
|
||||
local unistd = require("posix.unistd")
|
||||
local rand = require("openssl.rand")
|
||||
local hmac = require("openssl.hmac")
|
||||
|
||||
--
|
||||
--
|
||||
-- Configure these variables for your settings.
|
||||
--
|
||||
--
|
||||
|
||||
-- A list of password protected repositories along with the users who can access them.
|
||||
local protected_repos = {
|
||||
glouglou = { laurent = true, jason = true },
|
||||
qt = { jason = true, bob = true }
|
||||
}
|
||||
|
||||
-- A list of users and hashes, generated with `mkpasswd -m sha-512 -R 300000`.
|
||||
local users = {
|
||||
jason = "$6$rounds=300000$YYJct3n/o.ruYK$HhpSeuCuW1fJkpvMZOZzVizeLsBKcGA/aF2UPuV5v60JyH2MVSG6P511UMTj2F3H75.IT2HIlnvXzNb60FcZH1",
|
||||
laurent = "$6$rounds=300000$dP0KNHwYb3JKigT$pN/LG7rWxQ4HniFtx5wKyJXBJUKP7R01zTNZ0qSK/aivw8ywGAOdfYiIQFqFhZFtVGvr11/7an.nesvm8iJUi.",
|
||||
bob = "$6$rounds=300000$jCLCCt6LUpTz$PI1vvd1yaVYcCzqH8QAJFcJ60b6W/6sjcOsU7mAkNo7IE8FRGW1vkjF8I/T5jt/auv5ODLb1L4S2s.CAyZyUC"
|
||||
}
|
||||
|
||||
-- Set this to a path this script can write to for storing a persistent
|
||||
-- cookie secret, which should be guarded.
|
||||
local secret_filename = "/var/cache/cgit/auth-secret"
|
||||
|
||||
--
|
||||
--
|
||||
-- Authentication functions follow below. Swap these out if you want different authentication semantics.
|
||||
--
|
||||
--
|
||||
|
||||
-- Sets HTTP cookie headers based on post and sets up redirection.
|
||||
function authenticate_post()
|
||||
local hash = users[post["username"]]
|
||||
local redirect = validate_value("redirect", post["redirect"])
|
||||
|
||||
if redirect == nil then
|
||||
not_found()
|
||||
return 0
|
||||
end
|
||||
|
||||
redirect_to(redirect)
|
||||
|
||||
if hash == nil or hash ~= unistd.crypt(post["password"], hash) then
|
||||
set_cookie("cgitauth", "")
|
||||
else
|
||||
-- One week expiration time
|
||||
local username = secure_value("username", post["username"], os.time() + 604800)
|
||||
set_cookie("cgitauth", username)
|
||||
end
|
||||
|
||||
html("\n")
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
-- Returns 1 if the cookie is valid and 0 if it is not.
|
||||
function authenticate_cookie()
|
||||
accepted_users = protected_repos[cgit["repo"]]
|
||||
if accepted_users == nil then
|
||||
-- We return as valid if the repo is not protected.
|
||||
return 1
|
||||
end
|
||||
|
||||
local username = validate_value("username", get_cookie(http["cookie"], "cgitauth"))
|
||||
if username == nil or not accepted_users[username:lower()] then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
end
|
||||
end
|
||||
|
||||
-- Prints the html for the login form.
|
||||
function body()
|
||||
html("<h2>Authentication Required</h2>")
|
||||
html("<form method='post' action='")
|
||||
html_attr(cgit["login"])
|
||||
html("'>")
|
||||
html("<input type='hidden' name='redirect' value='")
|
||||
html_attr(secure_value("redirect", cgit["url"], 0))
|
||||
html("' />")
|
||||
html("<table>")
|
||||
html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
|
||||
html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
|
||||
html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
|
||||
html("</table></form>")
|
||||
|
||||
return 0
|
||||
end
|
||||
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Wrapper around filter API, exposing the http table, the cgit table, and the post table to the above functions.
|
||||
--
|
||||
--
|
||||
|
||||
local actions = {}
|
||||
actions["authenticate-post"] = authenticate_post
|
||||
actions["authenticate-cookie"] = authenticate_cookie
|
||||
actions["body"] = body
|
||||
|
||||
function filter_open(...)
|
||||
action = actions[select(1, ...)]
|
||||
|
||||
http = {}
|
||||
http["cookie"] = select(2, ...)
|
||||
http["method"] = select(3, ...)
|
||||
http["query"] = select(4, ...)
|
||||
http["referer"] = select(5, ...)
|
||||
http["path"] = select(6, ...)
|
||||
http["host"] = select(7, ...)
|
||||
http["https"] = select(8, ...)
|
||||
|
||||
cgit = {}
|
||||
cgit["repo"] = select(9, ...)
|
||||
cgit["page"] = select(10, ...)
|
||||
cgit["url"] = select(11, ...)
|
||||
cgit["login"] = select(12, ...)
|
||||
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
return action()
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
post = parse_qs(str)
|
||||
end
|
||||
|
||||
|
||||
--
|
||||
--
|
||||
-- Utility functions based on keplerproject/wsapi.
|
||||
--
|
||||
--
|
||||
|
||||
function url_decode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "+", " ")
|
||||
str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
|
||||
str = string.gsub(str, "\r\n", "\n")
|
||||
return str
|
||||
end
|
||||
|
||||
function url_encode(str)
|
||||
if not str then
|
||||
return ""
|
||||
end
|
||||
str = string.gsub(str, "\n", "\r\n")
|
||||
str = string.gsub(str, "([^%w ])", function(c) return string.format("%%%02X", string.byte(c)) end)
|
||||
str = string.gsub(str, " ", "+")
|
||||
return str
|
||||
end
|
||||
|
||||
function parse_qs(qs)
|
||||
local tab = {}
|
||||
for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
|
||||
tab[url_decode(key)] = url_decode(val)
|
||||
end
|
||||
return tab
|
||||
end
|
||||
|
||||
function get_cookie(cookies, name)
|
||||
cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
|
||||
return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
|
||||
end
|
||||
|
||||
function tohex(b)
|
||||
local x = ""
|
||||
for i = 1, #b do
|
||||
x = x .. string.format("%.2x", string.byte(b, i))
|
||||
end
|
||||
return x
|
||||
end
|
||||
|
||||
--
|
||||
--
|
||||
-- Cookie construction and validation helpers.
|
||||
--
|
||||
--
|
||||
|
||||
local secret = nil
|
||||
|
||||
-- Loads a secret from a file, creates a secret, or returns one from memory.
|
||||
function get_secret()
|
||||
if secret ~= nil then
|
||||
return secret
|
||||
end
|
||||
local secret_file = io.open(secret_filename, "r")
|
||||
if secret_file == nil then
|
||||
local old_umask = sysstat.umask(63)
|
||||
local temporary_filename = secret_filename .. ".tmp." .. tohex(rand.bytes(16))
|
||||
local temporary_file = io.open(temporary_filename, "w")
|
||||
if temporary_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
temporary_file:write(tohex(rand.bytes(32)))
|
||||
temporary_file:close()
|
||||
unistd.link(temporary_filename, secret_filename) -- Intentionally fails in the case that another process is doing the same.
|
||||
unistd.unlink(temporary_filename)
|
||||
sysstat.umask(old_umask)
|
||||
secret_file = io.open(secret_filename, "r")
|
||||
end
|
||||
if secret_file == nil then
|
||||
os.exit(177)
|
||||
end
|
||||
secret = secret_file:read()
|
||||
secret_file:close()
|
||||
if secret:len() ~= 64 then
|
||||
os.exit(177)
|
||||
end
|
||||
return secret
|
||||
end
|
||||
|
||||
-- Returns value of cookie if cookie is valid. Otherwise returns nil.
|
||||
function validate_value(expected_field, cookie)
|
||||
local i = 0
|
||||
local value = ""
|
||||
local field = ""
|
||||
local expiration = 0
|
||||
local salt = ""
|
||||
local chmac = ""
|
||||
|
||||
if cookie == nil or cookie:len() < 3 or cookie:sub(1, 1) == "|" then
|
||||
return nil
|
||||
end
|
||||
|
||||
for component in string.gmatch(cookie, "[^|]+") do
|
||||
if i == 0 then
|
||||
field = component
|
||||
elseif i == 1 then
|
||||
value = component
|
||||
elseif i == 2 then
|
||||
expiration = tonumber(component)
|
||||
if expiration == nil then
|
||||
expiration = -1
|
||||
end
|
||||
elseif i == 3 then
|
||||
salt = component
|
||||
elseif i == 4 then
|
||||
chmac = component
|
||||
else
|
||||
break
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
if chmac == nil or chmac:len() == 0 then
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Lua hashes strings, so these comparisons are time invariant.
|
||||
if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if expiration == -1 or (expiration ~= 0 and expiration <= os.time()) then
|
||||
return nil
|
||||
end
|
||||
|
||||
if url_decode(field) ~= expected_field then
|
||||
return nil
|
||||
end
|
||||
|
||||
return url_decode(value)
|
||||
end
|
||||
|
||||
function secure_value(field, value, expiration)
|
||||
if value == nil or value:len() <= 0 then
|
||||
return ""
|
||||
end
|
||||
|
||||
local authstr = ""
|
||||
local salt = tohex(rand.bytes(16))
|
||||
value = url_encode(value)
|
||||
field = url_encode(field)
|
||||
authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt
|
||||
authstr = authstr .. "|" .. tohex(hmac.new(get_secret(), "sha256"):final(authstr))
|
||||
return authstr
|
||||
end
|
||||
|
||||
function set_cookie(cookie, value)
|
||||
html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly")
|
||||
if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
|
||||
html("; secure")
|
||||
end
|
||||
html("\n")
|
||||
end
|
||||
|
||||
function redirect_to(url)
|
||||
html("Status: 302 Redirect\n")
|
||||
html("Cache-Control: no-cache, no-store\n")
|
||||
html("Location: " .. url .. "\n")
|
||||
end
|
||||
|
||||
function not_found()
|
||||
html("Status: 404 Not Found\n")
|
||||
html("Cache-Control: no-cache, no-store\n\n")
|
||||
end
|
55
filters/syntax-highlighting.py
Executable file
55
filters/syntax-highlighting.py
Executable file
|
@ -0,0 +1,55 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# This script uses Pygments and Python3. You must have both installed
|
||||
# for this to work.
|
||||
#
|
||||
# http://pygments.org/
|
||||
# http://python.org/
|
||||
#
|
||||
# It may be used with the source-filter or repo.source-filter settings
|
||||
# in cgitrc.
|
||||
#
|
||||
# The following environment variables can be used to retrieve the
|
||||
# configuration of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
|
||||
|
||||
import sys
|
||||
import io
|
||||
from pygments import highlight
|
||||
from pygments.util import ClassNotFound
|
||||
from pygments.lexers import TextLexer
|
||||
from pygments.lexers import guess_lexer
|
||||
from pygments.lexers import guess_lexer_for_filename
|
||||
from pygments.formatters import HtmlFormatter
|
||||
|
||||
|
||||
sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8', errors='replace')
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
|
||||
data = sys.stdin.read()
|
||||
filename = sys.argv[1]
|
||||
formatter = HtmlFormatter(style='pastie', nobackground=True)
|
||||
|
||||
try:
|
||||
lexer = guess_lexer_for_filename(filename, data)
|
||||
except ClassNotFound:
|
||||
# check if there is any shebang
|
||||
if data[0:2] == '#!':
|
||||
lexer = guess_lexer(data)
|
||||
else:
|
||||
lexer = TextLexer()
|
||||
except TypeError:
|
||||
lexer = TextLexer()
|
||||
|
||||
# highlight! :-)
|
||||
# printout pygments' css definitions as well
|
||||
sys.stdout.write('<style>')
|
||||
sys.stdout.write(formatter.get_style_defs('.highlight'))
|
||||
sys.stdout.write('</style>')
|
||||
sys.stdout.write(highlight(data, lexer, formatter, outfile=None))
|
121
filters/syntax-highlighting.sh
Executable file
121
filters/syntax-highlighting.sh
Executable file
|
@ -0,0 +1,121 @@
|
|||
#!/bin/sh
|
||||
# This script can be used to implement syntax highlighting in the cgit
|
||||
# tree-view by referring to this file with the source-filter or repo.source-
|
||||
# filter options in cgitrc.
|
||||
#
|
||||
# This script requires a shell supporting the ${var##pattern} syntax.
|
||||
# It is supported by at least dash and bash, however busybox environments
|
||||
# might have to use an external call to sed instead.
|
||||
#
|
||||
# Note: the highlight command (http://www.andre-simon.de/) uses css for syntax
|
||||
# highlighting, so you'll probably want something like the following included
|
||||
# in your css file:
|
||||
#
|
||||
# Style definition file generated by highlight 2.4.8, http://www.andre-simon.de/
|
||||
#
|
||||
# table.blob .num { color:#2928ff; }
|
||||
# table.blob .esc { color:#ff00ff; }
|
||||
# table.blob .str { color:#ff0000; }
|
||||
# table.blob .dstr { color:#818100; }
|
||||
# table.blob .slc { color:#838183; font-style:italic; }
|
||||
# table.blob .com { color:#838183; font-style:italic; }
|
||||
# table.blob .dir { color:#008200; }
|
||||
# table.blob .sym { color:#000000; }
|
||||
# table.blob .kwa { color:#000000; font-weight:bold; }
|
||||
# table.blob .kwb { color:#830000; }
|
||||
# table.blob .kwc { color:#000000; font-weight:bold; }
|
||||
# table.blob .kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# Style definition file generated by highlight 2.6.14, http://www.andre-simon.de/
|
||||
#
|
||||
# body.hl { background-color:#ffffff; }
|
||||
# pre.hl { color:#000000; background-color:#ffffff; font-size:10pt; font-family:'Courier New';}
|
||||
# .hl.num { color:#2928ff; }
|
||||
# .hl.esc { color:#ff00ff; }
|
||||
# .hl.str { color:#ff0000; }
|
||||
# .hl.dstr { color:#818100; }
|
||||
# .hl.slc { color:#838183; font-style:italic; }
|
||||
# .hl.com { color:#838183; font-style:italic; }
|
||||
# .hl.dir { color:#008200; }
|
||||
# .hl.sym { color:#000000; }
|
||||
# .hl.line { color:#555555; }
|
||||
# .hl.mark { background-color:#ffffbb;}
|
||||
# .hl.kwa { color:#000000; font-weight:bold; }
|
||||
# .hl.kwb { color:#830000; }
|
||||
# .hl.kwc { color:#000000; font-weight:bold; }
|
||||
# .hl.kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# Style definition file generated by highlight 3.8, http://www.andre-simon.de/
|
||||
#
|
||||
# body.hl { background-color:#e0eaee; }
|
||||
# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New';}
|
||||
# .hl.num { color:#b07e00; }
|
||||
# .hl.esc { color:#ff00ff; }
|
||||
# .hl.str { color:#bf0303; }
|
||||
# .hl.pps { color:#818100; }
|
||||
# .hl.slc { color:#838183; font-style:italic; }
|
||||
# .hl.com { color:#838183; font-style:italic; }
|
||||
# .hl.ppc { color:#008200; }
|
||||
# .hl.opt { color:#000000; }
|
||||
# .hl.lin { color:#555555; }
|
||||
# .hl.kwa { color:#000000; font-weight:bold; }
|
||||
# .hl.kwb { color:#0057ae; }
|
||||
# .hl.kwc { color:#000000; font-weight:bold; }
|
||||
# .hl.kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# Style definition file generated by highlight 3.13, http://www.andre-simon.de/
|
||||
#
|
||||
# body.hl { background-color:#e0eaee; }
|
||||
# pre.hl { color:#000000; background-color:#e0eaee; font-size:10pt; font-family:'Courier New',monospace;}
|
||||
# .hl.num { color:#b07e00; }
|
||||
# .hl.esc { color:#ff00ff; }
|
||||
# .hl.str { color:#bf0303; }
|
||||
# .hl.pps { color:#818100; }
|
||||
# .hl.slc { color:#838183; font-style:italic; }
|
||||
# .hl.com { color:#838183; font-style:italic; }
|
||||
# .hl.ppc { color:#008200; }
|
||||
# .hl.opt { color:#000000; }
|
||||
# .hl.ipl { color:#0057ae; }
|
||||
# .hl.lin { color:#555555; }
|
||||
# .hl.kwa { color:#000000; font-weight:bold; }
|
||||
# .hl.kwb { color:#0057ae; }
|
||||
# .hl.kwc { color:#000000; font-weight:bold; }
|
||||
# .hl.kwd { color:#010181; }
|
||||
#
|
||||
#
|
||||
# The following environment variables can be used to retrieve the configuration
|
||||
# of the repository for which this script is called:
|
||||
# CGIT_REPO_URL ( = repo.url setting )
|
||||
# CGIT_REPO_NAME ( = repo.name setting )
|
||||
# CGIT_REPO_PATH ( = repo.path setting )
|
||||
# CGIT_REPO_OWNER ( = repo.owner setting )
|
||||
# CGIT_REPO_DEFBRANCH ( = repo.defbranch setting )
|
||||
# CGIT_REPO_SECTION ( = section setting )
|
||||
# CGIT_REPO_CLONE_URL ( = repo.clone-url setting )
|
||||
#
|
||||
|
||||
# store filename and extension in local vars
|
||||
BASENAME="$1"
|
||||
EXTENSION="${BASENAME##*.}"
|
||||
|
||||
[ "${BASENAME}" = "${EXTENSION}" ] && EXTENSION=txt
|
||||
[ -z "${EXTENSION}" ] && EXTENSION=txt
|
||||
|
||||
# map Makefile and Makefile.* to .mk
|
||||
[ "${BASENAME%%.*}" = "Makefile" ] && EXTENSION=mk
|
||||
|
||||
# highlight versions 2 and 3 have different commandline options. Specifically,
|
||||
# the -X option that is used for version 2 is replaced by the -O xhtml option
|
||||
# for version 3.
|
||||
#
|
||||
# Version 2 can be found (for example) on EPEL 5, while version 3 can be
|
||||
# found (for example) on EPEL 6.
|
||||
#
|
||||
# This is for version 2
|
||||
exec highlight --force -f -I -X -S "$EXTENSION" 2>/dev/null
|
||||
|
||||
# This is for version 3
|
||||
#exec highlight --force -f -I -O xhtml -S "$EXTENSION" 2>/dev/null
|
20
gen-version.sh
Executable file
20
gen-version.sh
Executable file
|
@ -0,0 +1,20 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Get version-info specified in Makefile
|
||||
V=$1
|
||||
|
||||
# Use `git describe` to get current version if we're inside a git repo
|
||||
if test "$(git rev-parse --git-dir 2>/dev/null)" = '.git'
|
||||
then
|
||||
V=$(git describe --abbrev=4 HEAD 2>/dev/null)
|
||||
fi
|
||||
|
||||
new="CGIT_VERSION = $V"
|
||||
old=$(cat VERSION 2>/dev/null)
|
||||
|
||||
# Exit if VERSION is uptodate
|
||||
test "$old" = "$new" && exit 0
|
||||
|
||||
# Update VERSION with new version-info
|
||||
echo "$new" > VERSION
|
||||
cat VERSION
|
1
git
Submodule
1
git
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 5fa0f5238b0cd46cfe7f6fa76c3f526ea98148d9
|
344
html.c
Normal file
344
html.c
Normal file
|
@ -0,0 +1,344 @@
|
|||
/* html.c: helper functions for html output
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "html.h"
|
||||
#include "url.h"
|
||||
|
||||
/* Percent-encoding of each character, except: a-zA-Z0-9!$()*,./:;@- */
|
||||
static const char* url_escape_table[256] = {
|
||||
"%00", "%01", "%02", "%03", "%04", "%05", "%06", "%07",
|
||||
"%08", "%09", "%0a", "%0b", "%0c", "%0d", "%0e", "%0f",
|
||||
"%10", "%11", "%12", "%13", "%14", "%15", "%16", "%17",
|
||||
"%18", "%19", "%1a", "%1b", "%1c", "%1d", "%1e", "%1f",
|
||||
"%20", NULL, "%22", "%23", NULL, "%25", "%26", "%27",
|
||||
NULL, NULL, NULL, "%2b", NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, "%3c", "%3d", "%3e", "%3f",
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, "%5c", NULL, "%5e", NULL,
|
||||
"%60", NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
|
||||
NULL, NULL, NULL, "%7b", "%7c", "%7d", NULL, "%7f",
|
||||
"%80", "%81", "%82", "%83", "%84", "%85", "%86", "%87",
|
||||
"%88", "%89", "%8a", "%8b", "%8c", "%8d", "%8e", "%8f",
|
||||
"%90", "%91", "%92", "%93", "%94", "%95", "%96", "%97",
|
||||
"%98", "%99", "%9a", "%9b", "%9c", "%9d", "%9e", "%9f",
|
||||
"%a0", "%a1", "%a2", "%a3", "%a4", "%a5", "%a6", "%a7",
|
||||
"%a8", "%a9", "%aa", "%ab", "%ac", "%ad", "%ae", "%af",
|
||||
"%b0", "%b1", "%b2", "%b3", "%b4", "%b5", "%b6", "%b7",
|
||||
"%b8", "%b9", "%ba", "%bb", "%bc", "%bd", "%be", "%bf",
|
||||
"%c0", "%c1", "%c2", "%c3", "%c4", "%c5", "%c6", "%c7",
|
||||
"%c8", "%c9", "%ca", "%cb", "%cc", "%cd", "%ce", "%cf",
|
||||
"%d0", "%d1", "%d2", "%d3", "%d4", "%d5", "%d6", "%d7",
|
||||
"%d8", "%d9", "%da", "%db", "%dc", "%dd", "%de", "%df",
|
||||
"%e0", "%e1", "%e2", "%e3", "%e4", "%e5", "%e6", "%e7",
|
||||
"%e8", "%e9", "%ea", "%eb", "%ec", "%ed", "%ee", "%ef",
|
||||
"%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
|
||||
"%f8", "%f9", "%fa", "%fb", "%fc", "%fd", "%fe", "%ff"
|
||||
};
|
||||
|
||||
char *fmt(const char *format, ...)
|
||||
{
|
||||
static char buf[8][1024];
|
||||
static int bufidx;
|
||||
int len;
|
||||
va_list args;
|
||||
|
||||
bufidx++;
|
||||
bufidx &= 7;
|
||||
|
||||
va_start(args, format);
|
||||
len = vsnprintf(buf[bufidx], sizeof(buf[bufidx]), format, args);
|
||||
va_end(args);
|
||||
if (len > sizeof(buf[bufidx])) {
|
||||
fprintf(stderr, "[html.c] string truncated: %s\n", format);
|
||||
exit(1);
|
||||
}
|
||||
return buf[bufidx];
|
||||
}
|
||||
|
||||
char *fmtalloc(const char *format, ...)
|
||||
{
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
strbuf_vaddf(&sb, format, args);
|
||||
va_end(args);
|
||||
|
||||
return strbuf_detach(&sb, NULL);
|
||||
}
|
||||
|
||||
void html_raw(const char *data, size_t size)
|
||||
{
|
||||
if (write(STDOUT_FILENO, data, size) != size)
|
||||
die_errno("write error on html output");
|
||||
}
|
||||
|
||||
void html(const char *txt)
|
||||
{
|
||||
html_raw(txt, strlen(txt));
|
||||
}
|
||||
|
||||
void htmlf(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
va_start(args, format);
|
||||
strbuf_vaddf(&buf, format, args);
|
||||
va_end(args);
|
||||
html(buf.buf);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
void html_txtf(const char *format, ...)
|
||||
{
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
html_vtxtf(format, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
void html_vtxtf(const char *format, va_list ap)
|
||||
{
|
||||
va_list cp;
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
|
||||
va_copy(cp, ap);
|
||||
strbuf_vaddf(&buf, format, cp);
|
||||
va_end(cp);
|
||||
html_txt(buf.buf);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
|
||||
void html_txt(const char *txt)
|
||||
{
|
||||
if (txt)
|
||||
html_ntxt(txt, strlen(txt));
|
||||
}
|
||||
|
||||
ssize_t html_ntxt(const char *txt, size_t len)
|
||||
{
|
||||
const char *t = txt;
|
||||
ssize_t slen;
|
||||
|
||||
if (len > SSIZE_MAX)
|
||||
return -1;
|
||||
|
||||
slen = (ssize_t) len;
|
||||
while (t && *t && slen--) {
|
||||
int c = *t;
|
||||
if (c == '<' || c == '>' || c == '&') {
|
||||
html_raw(txt, t - txt);
|
||||
if (c == '>')
|
||||
html(">");
|
||||
else if (c == '<')
|
||||
html("<");
|
||||
else if (c == '&')
|
||||
html("&");
|
||||
txt = t + 1;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
if (t != txt)
|
||||
html_raw(txt, t - txt);
|
||||
return slen;
|
||||
}
|
||||
|
||||
void html_attrf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
va_start(ap, fmt);
|
||||
strbuf_vaddf(&sb, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
html_attr(sb.buf);
|
||||
strbuf_release(&sb);
|
||||
}
|
||||
|
||||
void html_attr(const char *txt)
|
||||
{
|
||||
const char *t = txt;
|
||||
while (t && *t) {
|
||||
int c = *t;
|
||||
if (c == '<' || c == '>' || c == '\'' || c == '\"' || c == '&') {
|
||||
html_raw(txt, t - txt);
|
||||
if (c == '>')
|
||||
html(">");
|
||||
else if (c == '<')
|
||||
html("<");
|
||||
else if (c == '\'')
|
||||
html("'");
|
||||
else if (c == '"')
|
||||
html(""");
|
||||
else if (c == '&')
|
||||
html("&");
|
||||
txt = t + 1;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
if (t != txt)
|
||||
html(txt);
|
||||
}
|
||||
|
||||
void html_url_path(const char *txt)
|
||||
{
|
||||
const char *t = txt;
|
||||
while (t && *t) {
|
||||
unsigned char c = *t;
|
||||
const char *e = url_escape_table[c];
|
||||
if (e && c != '+' && c != '&') {
|
||||
html_raw(txt, t - txt);
|
||||
html(e);
|
||||
txt = t + 1;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
if (t != txt)
|
||||
html(txt);
|
||||
}
|
||||
|
||||
void html_url_arg(const char *txt)
|
||||
{
|
||||
const char *t = txt;
|
||||
while (t && *t) {
|
||||
unsigned char c = *t;
|
||||
const char *e = url_escape_table[c];
|
||||
if (c == ' ')
|
||||
e = "+";
|
||||
if (e) {
|
||||
html_raw(txt, t - txt);
|
||||
html(e);
|
||||
txt = t + 1;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
if (t != txt)
|
||||
html(txt);
|
||||
}
|
||||
|
||||
void html_header_arg_in_quotes(const char *txt)
|
||||
{
|
||||
const char *t = txt;
|
||||
while (t && *t) {
|
||||
unsigned char c = *t;
|
||||
const char *e = NULL;
|
||||
if (c == '\\')
|
||||
e = "\\\\";
|
||||
else if (c == '\r')
|
||||
e = "\\r";
|
||||
else if (c == '\n')
|
||||
e = "\\n";
|
||||
else if (c == '"')
|
||||
e = "\\\"";
|
||||
if (e) {
|
||||
html_raw(txt, t - txt);
|
||||
html(e);
|
||||
txt = t + 1;
|
||||
}
|
||||
t++;
|
||||
}
|
||||
if (t != txt)
|
||||
html(txt);
|
||||
|
||||
}
|
||||
|
||||
void html_hidden(const char *name, const char *value)
|
||||
{
|
||||
html("<input type='hidden' name='");
|
||||
html_attr(name);
|
||||
html("' value='");
|
||||
html_attr(value);
|
||||
html("'/>");
|
||||
}
|
||||
|
||||
void html_option(const char *value, const char *text, const char *selected_value)
|
||||
{
|
||||
html("<option value='");
|
||||
html_attr(value);
|
||||
html("'");
|
||||
if (selected_value && !strcmp(selected_value, value))
|
||||
html(" selected='selected'");
|
||||
html(">");
|
||||
html_txt(text);
|
||||
html("</option>\n");
|
||||
}
|
||||
|
||||
void html_intoption(int value, const char *text, int selected_value)
|
||||
{
|
||||
htmlf("<option value='%d'%s>", value,
|
||||
value == selected_value ? " selected='selected'" : "");
|
||||
html_txt(text);
|
||||
html("</option>");
|
||||
}
|
||||
|
||||
void html_link_open(const char *url, const char *title, const char *class)
|
||||
{
|
||||
html("<a href='");
|
||||
html_attr(url);
|
||||
if (title) {
|
||||
html("' title='");
|
||||
html_attr(title);
|
||||
}
|
||||
if (class) {
|
||||
html("' class='");
|
||||
html_attr(class);
|
||||
}
|
||||
html("'>");
|
||||
}
|
||||
|
||||
void html_link_close(void)
|
||||
{
|
||||
html("</a>");
|
||||
}
|
||||
|
||||
void html_fileperm(unsigned short mode)
|
||||
{
|
||||
htmlf("%c%c%c", (mode & 4 ? 'r' : '-'),
|
||||
(mode & 2 ? 'w' : '-'), (mode & 1 ? 'x' : '-'));
|
||||
}
|
||||
|
||||
int html_include(const char *filename)
|
||||
{
|
||||
FILE *f;
|
||||
char buf[4096];
|
||||
size_t len;
|
||||
|
||||
if (!(f = fopen(filename, "r"))) {
|
||||
fprintf(stderr, "[cgit] Failed to include file %s: %s (%d).\n",
|
||||
filename, strerror(errno), errno);
|
||||
return -1;
|
||||
}
|
||||
while ((len = fread(buf, 1, 4096, f)) > 0)
|
||||
html_raw(buf, len);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value))
|
||||
{
|
||||
const char *t = txt;
|
||||
|
||||
while (t && *t) {
|
||||
char *name = url_decode_parameter_name(&t);
|
||||
if (*name) {
|
||||
char *value = url_decode_parameter_value(&t);
|
||||
fn(name, value);
|
||||
free(value);
|
||||
}
|
||||
free(name);
|
||||
}
|
||||
}
|
37
html.h
Normal file
37
html.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
#ifndef HTML_H
|
||||
#define HTML_H
|
||||
|
||||
#include "cgit.h"
|
||||
|
||||
extern void html_raw(const char *txt, size_t size);
|
||||
extern void html(const char *txt);
|
||||
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern void htmlf(const char *format,...);
|
||||
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern void html_txtf(const char *format,...);
|
||||
|
||||
__attribute__((format (printf,1,0)))
|
||||
extern void html_vtxtf(const char *format, va_list ap);
|
||||
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern void html_attrf(const char *format,...);
|
||||
|
||||
extern void html_txt(const char *txt);
|
||||
extern ssize_t html_ntxt(const char *txt, size_t len);
|
||||
extern void html_attr(const char *txt);
|
||||
extern void html_url_path(const char *txt);
|
||||
extern void html_url_arg(const char *txt);
|
||||
extern void html_header_arg_in_quotes(const char *txt);
|
||||
extern void html_hidden(const char *name, const char *value);
|
||||
extern void html_option(const char *value, const char *text, const char *selected_value);
|
||||
extern void html_intoption(int value, const char *text, int selected_value);
|
||||
extern void html_link_open(const char *url, const char *title, const char *class);
|
||||
extern void html_link_close(void);
|
||||
extern void html_fileperm(unsigned short mode);
|
||||
extern int html_include(const char *filename);
|
||||
|
||||
extern void http_parse_querystring(const char *txt, void (*fn)(const char *name, const char *value));
|
||||
|
||||
#endif /* HTML_H */
|
224
parsing.c
Normal file
224
parsing.c
Normal file
|
@ -0,0 +1,224 @@
|
|||
/* parsing.c: parsing of config files
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
|
||||
/*
|
||||
* url syntax: [repo ['/' cmd [ '/' path]]]
|
||||
* repo: any valid repo url, may contain '/'
|
||||
* cmd: log | commit | diff | tree | view | blob | snapshot
|
||||
* path: any valid path, may contain '/'
|
||||
*
|
||||
*/
|
||||
void cgit_parse_url(const char *url)
|
||||
{
|
||||
char *c, *cmd, *p;
|
||||
struct cgit_repo *repo;
|
||||
|
||||
if (!url || url[0] == '\0')
|
||||
return;
|
||||
|
||||
ctx.qry.page = NULL;
|
||||
ctx.repo = cgit_get_repoinfo(url);
|
||||
if (ctx.repo) {
|
||||
ctx.qry.repo = ctx.repo->url;
|
||||
return;
|
||||
}
|
||||
|
||||
cmd = NULL;
|
||||
c = strchr(url, '/');
|
||||
while (c) {
|
||||
c[0] = '\0';
|
||||
repo = cgit_get_repoinfo(url);
|
||||
if (repo) {
|
||||
ctx.repo = repo;
|
||||
cmd = c;
|
||||
}
|
||||
c[0] = '/';
|
||||
c = strchr(c + 1, '/');
|
||||
}
|
||||
|
||||
if (ctx.repo) {
|
||||
ctx.qry.repo = ctx.repo->url;
|
||||
p = strchr(cmd + 1, '/');
|
||||
if (p) {
|
||||
p[0] = '\0';
|
||||
if (p[1])
|
||||
ctx.qry.path = trim_end(p + 1, '/');
|
||||
}
|
||||
if (cmd[1])
|
||||
ctx.qry.page = xstrdup(cmd + 1);
|
||||
}
|
||||
}
|
||||
|
||||
static char *substr(const char *head, const char *tail)
|
||||
{
|
||||
char *buf;
|
||||
|
||||
if (tail < head)
|
||||
return xstrdup("");
|
||||
buf = xmalloc(tail - head + 1);
|
||||
strlcpy(buf, head, tail - head + 1);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void parse_user(const char *t, char **name, char **email, unsigned long *date, int *tz)
|
||||
{
|
||||
struct ident_split ident;
|
||||
unsigned email_len;
|
||||
|
||||
if (!split_ident_line(&ident, t, strchrnul(t, '\n') - t)) {
|
||||
*name = substr(ident.name_begin, ident.name_end);
|
||||
|
||||
email_len = ident.mail_end - ident.mail_begin;
|
||||
*email = xmalloc(strlen("<") + email_len + strlen(">") + 1);
|
||||
xsnprintf(*email, email_len + 3, "<%.*s>", email_len, ident.mail_begin);
|
||||
|
||||
if (ident.date_begin)
|
||||
*date = strtoul(ident.date_begin, NULL, 10);
|
||||
if (ident.tz_begin)
|
||||
*tz = atoi(ident.tz_begin);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef NO_ICONV
|
||||
#define reencode(a, b, c)
|
||||
#else
|
||||
static const char *reencode(char **txt, const char *src_enc, const char *dst_enc)
|
||||
{
|
||||
char *tmp;
|
||||
|
||||
if (!txt)
|
||||
return NULL;
|
||||
|
||||
if (!*txt || !src_enc || !dst_enc)
|
||||
return *txt;
|
||||
|
||||
/* no encoding needed if src_enc equals dst_enc */
|
||||
if (!strcasecmp(src_enc, dst_enc))
|
||||
return *txt;
|
||||
|
||||
tmp = reencode_string(*txt, dst_enc, src_enc);
|
||||
if (tmp) {
|
||||
free(*txt);
|
||||
*txt = tmp;
|
||||
}
|
||||
return *txt;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const char *next_header_line(const char *p)
|
||||
{
|
||||
p = strchr(p, '\n');
|
||||
if (!p)
|
||||
return NULL;
|
||||
return p + 1;
|
||||
}
|
||||
|
||||
static int end_of_header(const char *p)
|
||||
{
|
||||
return !p || (*p == '\n');
|
||||
}
|
||||
|
||||
struct commitinfo *cgit_parse_commit(struct commit *commit)
|
||||
{
|
||||
const int sha1hex_len = 40;
|
||||
struct commitinfo *ret;
|
||||
const char *p = get_cached_commit_buffer(the_repository, commit, NULL);
|
||||
const char *t;
|
||||
|
||||
ret = xcalloc(1, sizeof(struct commitinfo));
|
||||
ret->commit = commit;
|
||||
|
||||
if (!p)
|
||||
return ret;
|
||||
|
||||
if (!skip_prefix(p, "tree ", &p))
|
||||
die("Bad commit: %s", oid_to_hex(&commit->object.oid));
|
||||
p += sha1hex_len + 1;
|
||||
|
||||
while (skip_prefix(p, "parent ", &p))
|
||||
p += sha1hex_len + 1;
|
||||
|
||||
if (p && skip_prefix(p, "author ", &p)) {
|
||||
parse_user(p, &ret->author, &ret->author_email,
|
||||
&ret->author_date, &ret->author_tz);
|
||||
p = next_header_line(p);
|
||||
}
|
||||
|
||||
if (p && skip_prefix(p, "committer ", &p)) {
|
||||
parse_user(p, &ret->committer, &ret->committer_email,
|
||||
&ret->committer_date, &ret->committer_tz);
|
||||
p = next_header_line(p);
|
||||
}
|
||||
|
||||
if (p && skip_prefix(p, "encoding ", &p)) {
|
||||
t = strchr(p, '\n');
|
||||
if (t) {
|
||||
ret->msg_encoding = substr(p, t + 1);
|
||||
p = t + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ret->msg_encoding)
|
||||
ret->msg_encoding = xstrdup("UTF-8");
|
||||
|
||||
while (!end_of_header(p))
|
||||
p = next_header_line(p);
|
||||
while (p && *p == '\n')
|
||||
p++;
|
||||
if (!p)
|
||||
return ret;
|
||||
|
||||
t = strchrnul(p, '\n');
|
||||
ret->subject = substr(p, t);
|
||||
while (*t == '\n')
|
||||
t++;
|
||||
ret->msg = xstrdup(t);
|
||||
|
||||
reencode(&ret->author, ret->msg_encoding, PAGE_ENCODING);
|
||||
reencode(&ret->author_email, ret->msg_encoding, PAGE_ENCODING);
|
||||
reencode(&ret->committer, ret->msg_encoding, PAGE_ENCODING);
|
||||
reencode(&ret->committer_email, ret->msg_encoding, PAGE_ENCODING);
|
||||
reencode(&ret->subject, ret->msg_encoding, PAGE_ENCODING);
|
||||
reencode(&ret->msg, ret->msg_encoding, PAGE_ENCODING);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct taginfo *cgit_parse_tag(struct tag *tag)
|
||||
{
|
||||
void *data;
|
||||
enum object_type type;
|
||||
unsigned long size;
|
||||
const char *p;
|
||||
struct taginfo *ret = NULL;
|
||||
|
||||
data = read_object_file(&tag->object.oid, &type, &size);
|
||||
if (!data || type != OBJ_TAG)
|
||||
goto cleanup;
|
||||
|
||||
ret = xcalloc(1, sizeof(struct taginfo));
|
||||
|
||||
for (p = data; !end_of_header(p); p = next_header_line(p)) {
|
||||
if (skip_prefix(p, "tagger ", &p)) {
|
||||
parse_user(p, &ret->tagger, &ret->tagger_email,
|
||||
&ret->tagger_date, &ret->tagger_tz);
|
||||
}
|
||||
}
|
||||
|
||||
while (p && *p == '\n')
|
||||
p++;
|
||||
|
||||
if (p && *p)
|
||||
ret->msg = xstrdup(p);
|
||||
|
||||
cleanup:
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
3
robots.txt
Normal file
3
robots.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
User-agent: *
|
||||
Disallow: /*/snapshot/*
|
||||
Allow: /
|
270
scan-tree.c
Normal file
270
scan-tree.c
Normal file
|
@ -0,0 +1,270 @@
|
|||
/* scan-tree.c
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "scan-tree.h"
|
||||
#include "configfile.h"
|
||||
#include "html.h"
|
||||
#include <config.h>
|
||||
|
||||
/* return 1 if path contains a objects/ directory and a HEAD file */
|
||||
static int is_git_dir(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
struct strbuf pathbuf = STRBUF_INIT;
|
||||
int result = 0;
|
||||
|
||||
strbuf_addf(&pathbuf, "%s/objects", path);
|
||||
if (stat(pathbuf.buf, &st)) {
|
||||
if (errno != ENOENT)
|
||||
fprintf(stderr, "Error checking path %s: %s (%d)\n",
|
||||
path, strerror(errno), errno);
|
||||
goto out;
|
||||
}
|
||||
if (!S_ISDIR(st.st_mode))
|
||||
goto out;
|
||||
|
||||
strbuf_reset(&pathbuf);
|
||||
strbuf_addf(&pathbuf, "%s/HEAD", path);
|
||||
if (stat(pathbuf.buf, &st)) {
|
||||
if (errno != ENOENT)
|
||||
fprintf(stderr, "Error checking path %s: %s (%d)\n",
|
||||
path, strerror(errno), errno);
|
||||
goto out;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode))
|
||||
goto out;
|
||||
|
||||
result = 1;
|
||||
out:
|
||||
strbuf_release(&pathbuf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static struct cgit_repo *repo;
|
||||
static repo_config_fn config_fn;
|
||||
|
||||
static void scan_tree_repo_config(const char *name, const char *value)
|
||||
{
|
||||
config_fn(repo, name, value);
|
||||
}
|
||||
|
||||
static int gitconfig_config(const char *key, const char *value, void *cb)
|
||||
{
|
||||
const char *name;
|
||||
|
||||
if (!strcmp(key, "gitweb.owner"))
|
||||
config_fn(repo, "owner", value);
|
||||
else if (!strcmp(key, "gitweb.description"))
|
||||
config_fn(repo, "desc", value);
|
||||
else if (!strcmp(key, "gitweb.category"))
|
||||
config_fn(repo, "section", value);
|
||||
else if (!strcmp(key, "gitweb.homepage"))
|
||||
config_fn(repo, "homepage", value);
|
||||
else if (skip_prefix(key, "cgit.", &name))
|
||||
config_fn(repo, name, value);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *xstrrchr(char *s, char *from, int c)
|
||||
{
|
||||
while (from >= s && *from != c)
|
||||
from--;
|
||||
return from < s ? NULL : from;
|
||||
}
|
||||
|
||||
static void add_repo(const char *base, struct strbuf *path, repo_config_fn fn)
|
||||
{
|
||||
struct stat st;
|
||||
struct passwd *pwd;
|
||||
size_t pathlen;
|
||||
struct strbuf rel = STRBUF_INIT;
|
||||
char *p, *slash;
|
||||
int n;
|
||||
size_t size;
|
||||
|
||||
if (stat(path->buf, &st)) {
|
||||
fprintf(stderr, "Error accessing %s: %s (%d)\n",
|
||||
path->buf, strerror(errno), errno);
|
||||
return;
|
||||
}
|
||||
|
||||
strbuf_addch(path, '/');
|
||||
pathlen = path->len;
|
||||
|
||||
if (ctx.cfg.strict_export) {
|
||||
strbuf_addstr(path, ctx.cfg.strict_export);
|
||||
if(stat(path->buf, &st))
|
||||
return;
|
||||
strbuf_setlen(path, pathlen);
|
||||
}
|
||||
|
||||
strbuf_addstr(path, "noweb");
|
||||
if (!stat(path->buf, &st))
|
||||
return;
|
||||
strbuf_setlen(path, pathlen);
|
||||
|
||||
if (!starts_with(path->buf, base))
|
||||
strbuf_addbuf(&rel, path);
|
||||
else
|
||||
strbuf_addstr(&rel, path->buf + strlen(base) + 1);
|
||||
|
||||
if (!strcmp(rel.buf + rel.len - 5, "/.git"))
|
||||
strbuf_setlen(&rel, rel.len - 5);
|
||||
else if (rel.len && rel.buf[rel.len - 1] == '/')
|
||||
strbuf_setlen(&rel, rel.len - 1);
|
||||
|
||||
repo = cgit_add_repo(rel.buf);
|
||||
config_fn = fn;
|
||||
if (ctx.cfg.enable_git_config) {
|
||||
strbuf_addstr(path, "config");
|
||||
git_config_from_file(gitconfig_config, path->buf, NULL);
|
||||
strbuf_setlen(path, pathlen);
|
||||
}
|
||||
|
||||
if (ctx.cfg.remove_suffix) {
|
||||
size_t urllen;
|
||||
strip_suffix(repo->url, ".git", &urllen);
|
||||
strip_suffix_mem(repo->url, &urllen, "/");
|
||||
repo->url[urllen] = '\0';
|
||||
}
|
||||
repo->path = xstrdup(path->buf);
|
||||
while (!repo->owner) {
|
||||
if ((pwd = getpwuid(st.st_uid)) == NULL) {
|
||||
fprintf(stderr, "Error reading owner-info for %s: %s (%d)\n",
|
||||
path->buf, strerror(errno), errno);
|
||||
break;
|
||||
}
|
||||
if (pwd->pw_gecos)
|
||||
if ((p = strchr(pwd->pw_gecos, ',')))
|
||||
*p = '\0';
|
||||
repo->owner = xstrdup(pwd->pw_gecos ? pwd->pw_gecos : pwd->pw_name);
|
||||
}
|
||||
|
||||
if (repo->desc == cgit_default_repo_desc || !repo->desc) {
|
||||
strbuf_addstr(path, "description");
|
||||
if (!stat(path->buf, &st))
|
||||
readfile(path->buf, &repo->desc, &size);
|
||||
strbuf_setlen(path, pathlen);
|
||||
}
|
||||
|
||||
if (ctx.cfg.section_from_path) {
|
||||
n = ctx.cfg.section_from_path;
|
||||
if (n > 0) {
|
||||
slash = rel.buf - 1;
|
||||
while (slash && n && (slash = strchr(slash + 1, '/')))
|
||||
n--;
|
||||
} else {
|
||||
slash = rel.buf + rel.len;
|
||||
while (slash && n && (slash = xstrrchr(rel.buf, slash - 1, '/')))
|
||||
n++;
|
||||
}
|
||||
if (slash && !n) {
|
||||
*slash = '\0';
|
||||
repo->section = xstrdup(rel.buf);
|
||||
*slash = '/';
|
||||
if (starts_with(repo->name, repo->section)) {
|
||||
repo->name += strlen(repo->section);
|
||||
if (*repo->name == '/')
|
||||
repo->name++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strbuf_addstr(path, "cgitrc");
|
||||
if (!stat(path->buf, &st))
|
||||
parse_configfile(path->buf, &scan_tree_repo_config);
|
||||
|
||||
strbuf_release(&rel);
|
||||
}
|
||||
|
||||
static void scan_path(const char *base, const char *path, repo_config_fn fn)
|
||||
{
|
||||
DIR *dir = opendir(path);
|
||||
struct dirent *ent;
|
||||
struct strbuf pathbuf = STRBUF_INIT;
|
||||
size_t pathlen = strlen(path);
|
||||
struct stat st;
|
||||
|
||||
if (!dir) {
|
||||
fprintf(stderr, "Error opening directory %s: %s (%d)\n",
|
||||
path, strerror(errno), errno);
|
||||
return;
|
||||
}
|
||||
|
||||
strbuf_add(&pathbuf, path, strlen(path));
|
||||
if (is_git_dir(pathbuf.buf)) {
|
||||
add_repo(base, &pathbuf, fn);
|
||||
goto end;
|
||||
}
|
||||
strbuf_addstr(&pathbuf, "/.git");
|
||||
if (is_git_dir(pathbuf.buf)) {
|
||||
add_repo(base, &pathbuf, fn);
|
||||
goto end;
|
||||
}
|
||||
/*
|
||||
* Add one because we don't want to lose the trailing '/' when we
|
||||
* reset the length of pathbuf in the loop below.
|
||||
*/
|
||||
pathlen++;
|
||||
while ((ent = readdir(dir)) != NULL) {
|
||||
if (ent->d_name[0] == '.') {
|
||||
if (ent->d_name[1] == '\0')
|
||||
continue;
|
||||
if (ent->d_name[1] == '.' && ent->d_name[2] == '\0')
|
||||
continue;
|
||||
if (!ctx.cfg.scan_hidden_path)
|
||||
continue;
|
||||
}
|
||||
strbuf_setlen(&pathbuf, pathlen);
|
||||
strbuf_addstr(&pathbuf, ent->d_name);
|
||||
if (stat(pathbuf.buf, &st)) {
|
||||
fprintf(stderr, "Error checking path %s: %s (%d)\n",
|
||||
pathbuf.buf, strerror(errno), errno);
|
||||
continue;
|
||||
}
|
||||
if (S_ISDIR(st.st_mode))
|
||||
scan_path(base, pathbuf.buf, fn);
|
||||
}
|
||||
end:
|
||||
strbuf_release(&pathbuf);
|
||||
closedir(dir);
|
||||
}
|
||||
|
||||
void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn)
|
||||
{
|
||||
struct strbuf line = STRBUF_INIT;
|
||||
FILE *projects;
|
||||
int err;
|
||||
|
||||
projects = fopen(projectsfile, "r");
|
||||
if (!projects) {
|
||||
fprintf(stderr, "Error opening projectsfile %s: %s (%d)\n",
|
||||
projectsfile, strerror(errno), errno);
|
||||
return;
|
||||
}
|
||||
while (strbuf_getline(&line, projects) != EOF) {
|
||||
if (!line.len)
|
||||
continue;
|
||||
strbuf_insert(&line, 0, "/", 1);
|
||||
strbuf_insert(&line, 0, path, strlen(path));
|
||||
scan_path(path, line.buf, fn);
|
||||
}
|
||||
if ((err = ferror(projects))) {
|
||||
fprintf(stderr, "Error reading from projectsfile %s: %s (%d)\n",
|
||||
projectsfile, strerror(err), err);
|
||||
}
|
||||
fclose(projects);
|
||||
strbuf_release(&line);
|
||||
}
|
||||
|
||||
void scan_tree(const char *path, repo_config_fn fn)
|
||||
{
|
||||
scan_path(path, path, fn);
|
||||
}
|
2
scan-tree.h
Normal file
2
scan-tree.h
Normal file
|
@ -0,0 +1,2 @@
|
|||
extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn);
|
||||
extern void scan_tree(const char *path, repo_config_fn fn);
|
579
shared.c
Normal file
579
shared.c
Normal file
|
@ -0,0 +1,579 @@
|
|||
/* shared.c: global vars + some callback functions
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
|
||||
struct cgit_repolist cgit_repolist;
|
||||
struct cgit_context ctx;
|
||||
|
||||
int chk_zero(int result, char *msg)
|
||||
{
|
||||
if (result != 0)
|
||||
die_errno("%s", msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
int chk_positive(int result, char *msg)
|
||||
{
|
||||
if (result <= 0)
|
||||
die_errno("%s", msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
int chk_non_negative(int result, char *msg)
|
||||
{
|
||||
if (result < 0)
|
||||
die_errno("%s", msg);
|
||||
return result;
|
||||
}
|
||||
|
||||
char *cgit_default_repo_desc = "[no description]";
|
||||
struct cgit_repo *cgit_add_repo(const char *url)
|
||||
{
|
||||
struct cgit_repo *ret;
|
||||
|
||||
if (++cgit_repolist.count > cgit_repolist.length) {
|
||||
if (cgit_repolist.length == 0)
|
||||
cgit_repolist.length = 8;
|
||||
else
|
||||
cgit_repolist.length *= 2;
|
||||
cgit_repolist.repos = xrealloc(cgit_repolist.repos,
|
||||
cgit_repolist.length *
|
||||
sizeof(struct cgit_repo));
|
||||
}
|
||||
|
||||
ret = &cgit_repolist.repos[cgit_repolist.count-1];
|
||||
memset(ret, 0, sizeof(struct cgit_repo));
|
||||
ret->url = trim_end(url, '/');
|
||||
ret->name = ret->url;
|
||||
ret->path = NULL;
|
||||
ret->desc = cgit_default_repo_desc;
|
||||
ret->extra_head_content = NULL;
|
||||
ret->owner = NULL;
|
||||
ret->homepage = NULL;
|
||||
ret->section = ctx.cfg.section;
|
||||
ret->snapshots = ctx.cfg.snapshots;
|
||||
ret->enable_blame = ctx.cfg.enable_blame;
|
||||
ret->enable_commit_graph = ctx.cfg.enable_commit_graph;
|
||||
ret->enable_log_filecount = ctx.cfg.enable_log_filecount;
|
||||
ret->enable_log_linecount = ctx.cfg.enable_log_linecount;
|
||||
ret->enable_remote_branches = ctx.cfg.enable_remote_branches;
|
||||
ret->enable_subject_links = ctx.cfg.enable_subject_links;
|
||||
ret->enable_html_serving = ctx.cfg.enable_html_serving;
|
||||
ret->max_stats = ctx.cfg.max_stats;
|
||||
ret->branch_sort = ctx.cfg.branch_sort;
|
||||
ret->commit_sort = ctx.cfg.commit_sort;
|
||||
ret->module_link = ctx.cfg.module_link;
|
||||
ret->readme = ctx.cfg.readme;
|
||||
ret->mtime = -1;
|
||||
ret->about_filter = ctx.cfg.about_filter;
|
||||
ret->commit_filter = ctx.cfg.commit_filter;
|
||||
ret->source_filter = ctx.cfg.source_filter;
|
||||
ret->email_filter = ctx.cfg.email_filter;
|
||||
ret->owner_filter = ctx.cfg.owner_filter;
|
||||
ret->clone_url = ctx.cfg.clone_url;
|
||||
ret->submodules.strdup_strings = 1;
|
||||
ret->hide = ret->ignore = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct cgit_repo *cgit_get_repoinfo(const char *url)
|
||||
{
|
||||
int i;
|
||||
struct cgit_repo *repo;
|
||||
|
||||
for (i = 0; i < cgit_repolist.count; i++) {
|
||||
repo = &cgit_repolist.repos[i];
|
||||
if (repo->ignore)
|
||||
continue;
|
||||
if (!strcmp(repo->url, url))
|
||||
return repo;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void cgit_free_commitinfo(struct commitinfo *info)
|
||||
{
|
||||
free(info->author);
|
||||
free(info->author_email);
|
||||
free(info->committer);
|
||||
free(info->committer_email);
|
||||
free(info->subject);
|
||||
free(info->msg);
|
||||
free(info->msg_encoding);
|
||||
free(info);
|
||||
}
|
||||
|
||||
char *trim_end(const char *str, char c)
|
||||
{
|
||||
int len;
|
||||
|
||||
if (str == NULL)
|
||||
return NULL;
|
||||
len = strlen(str);
|
||||
while (len > 0 && str[len - 1] == c)
|
||||
len--;
|
||||
if (len == 0)
|
||||
return NULL;
|
||||
return xstrndup(str, len);
|
||||
}
|
||||
|
||||
char *ensure_end(const char *str, char c)
|
||||
{
|
||||
size_t len = strlen(str);
|
||||
char *result;
|
||||
|
||||
if (len && str[len - 1] == c)
|
||||
return xstrndup(str, len);
|
||||
|
||||
result = xmalloc(len + 2);
|
||||
memcpy(result, str, len);
|
||||
result[len] = '/';
|
||||
result[len + 1] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
void strbuf_ensure_end(struct strbuf *sb, char c)
|
||||
{
|
||||
if (!sb->len || sb->buf[sb->len - 1] != c)
|
||||
strbuf_addch(sb, c);
|
||||
}
|
||||
|
||||
void cgit_add_ref(struct reflist *list, struct refinfo *ref)
|
||||
{
|
||||
size_t size;
|
||||
|
||||
if (list->count >= list->alloc) {
|
||||
list->alloc += (list->alloc ? list->alloc : 4);
|
||||
size = list->alloc * sizeof(struct refinfo *);
|
||||
list->refs = xrealloc(list->refs, size);
|
||||
}
|
||||
list->refs[list->count++] = ref;
|
||||
}
|
||||
|
||||
static struct refinfo *cgit_mk_refinfo(const char *refname, const struct object_id *oid)
|
||||
{
|
||||
struct refinfo *ref;
|
||||
|
||||
ref = xmalloc(sizeof (struct refinfo));
|
||||
ref->refname = xstrdup(refname);
|
||||
ref->object = parse_object(the_repository, oid);
|
||||
switch (ref->object->type) {
|
||||
case OBJ_TAG:
|
||||
ref->tag = cgit_parse_tag((struct tag *)ref->object);
|
||||
break;
|
||||
case OBJ_COMMIT:
|
||||
ref->commit = cgit_parse_commit((struct commit *)ref->object);
|
||||
break;
|
||||
}
|
||||
return ref;
|
||||
}
|
||||
|
||||
void cgit_free_taginfo(struct taginfo *tag)
|
||||
{
|
||||
if (tag->tagger)
|
||||
free(tag->tagger);
|
||||
if (tag->tagger_email)
|
||||
free(tag->tagger_email);
|
||||
if (tag->msg)
|
||||
free(tag->msg);
|
||||
free(tag);
|
||||
}
|
||||
|
||||
static void cgit_free_refinfo(struct refinfo *ref)
|
||||
{
|
||||
if (ref->refname)
|
||||
free((char *)ref->refname);
|
||||
switch (ref->object->type) {
|
||||
case OBJ_TAG:
|
||||
cgit_free_taginfo(ref->tag);
|
||||
break;
|
||||
case OBJ_COMMIT:
|
||||
cgit_free_commitinfo(ref->commit);
|
||||
break;
|
||||
}
|
||||
free(ref);
|
||||
}
|
||||
|
||||
void cgit_free_reflist_inner(struct reflist *list)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < list->count; i++) {
|
||||
cgit_free_refinfo(list->refs[i]);
|
||||
}
|
||||
free(list->refs);
|
||||
}
|
||||
|
||||
int cgit_refs_cb(const char *refname, const struct object_id *oid, int flags,
|
||||
void *cb_data)
|
||||
{
|
||||
struct reflist *list = (struct reflist *)cb_data;
|
||||
struct refinfo *info = cgit_mk_refinfo(refname, oid);
|
||||
|
||||
if (info)
|
||||
cgit_add_ref(list, info);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cgit_diff_tree_cb(struct diff_queue_struct *q,
|
||||
struct diff_options *options, void *data)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < q->nr; i++) {
|
||||
if (q->queue[i]->status == 'U')
|
||||
continue;
|
||||
((filepair_fn)data)(q->queue[i]);
|
||||
}
|
||||
}
|
||||
|
||||
static int load_mmfile(mmfile_t *file, const struct object_id *oid)
|
||||
{
|
||||
enum object_type type;
|
||||
|
||||
if (is_null_oid(oid)) {
|
||||
file->ptr = (char *)"";
|
||||
file->size = 0;
|
||||
} else {
|
||||
file->ptr = read_object_file(oid, &type,
|
||||
(unsigned long *)&file->size);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive diff-buffers from xdiff and concatenate them as
|
||||
* needed across multiple callbacks.
|
||||
*
|
||||
* This is basically a copy of xdiff-interface.c/xdiff_outf(),
|
||||
* ripped from git and modified to use globals instead of
|
||||
* a special callback-struct.
|
||||
*/
|
||||
static char *diffbuf = NULL;
|
||||
static int buflen = 0;
|
||||
|
||||
static int filediff_cb(void *priv, mmbuffer_t *mb, int nbuf)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nbuf; i++) {
|
||||
if (mb[i].ptr[mb[i].size-1] != '\n') {
|
||||
/* Incomplete line */
|
||||
diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
|
||||
memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
|
||||
buflen += mb[i].size;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* we have a complete line */
|
||||
if (!diffbuf) {
|
||||
((linediff_fn)priv)(mb[i].ptr, mb[i].size);
|
||||
continue;
|
||||
}
|
||||
diffbuf = xrealloc(diffbuf, buflen + mb[i].size);
|
||||
memcpy(diffbuf + buflen, mb[i].ptr, mb[i].size);
|
||||
((linediff_fn)priv)(diffbuf, buflen + mb[i].size);
|
||||
free(diffbuf);
|
||||
diffbuf = NULL;
|
||||
buflen = 0;
|
||||
}
|
||||
if (diffbuf) {
|
||||
((linediff_fn)priv)(diffbuf, buflen);
|
||||
free(diffbuf);
|
||||
diffbuf = NULL;
|
||||
buflen = 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cgit_diff_files(const struct object_id *old_oid,
|
||||
const struct object_id *new_oid, unsigned long *old_size,
|
||||
unsigned long *new_size, int *binary, int context,
|
||||
int ignorews, linediff_fn fn)
|
||||
{
|
||||
mmfile_t file1, file2;
|
||||
xpparam_t diff_params;
|
||||
xdemitconf_t emit_params;
|
||||
xdemitcb_t emit_cb;
|
||||
|
||||
if (!load_mmfile(&file1, old_oid) || !load_mmfile(&file2, new_oid))
|
||||
return 1;
|
||||
|
||||
*old_size = file1.size;
|
||||
*new_size = file2.size;
|
||||
|
||||
if ((file1.ptr && buffer_is_binary(file1.ptr, file1.size)) ||
|
||||
(file2.ptr && buffer_is_binary(file2.ptr, file2.size))) {
|
||||
*binary = 1;
|
||||
if (file1.size)
|
||||
free(file1.ptr);
|
||||
if (file2.size)
|
||||
free(file2.ptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(&diff_params, 0, sizeof(diff_params));
|
||||
memset(&emit_params, 0, sizeof(emit_params));
|
||||
memset(&emit_cb, 0, sizeof(emit_cb));
|
||||
diff_params.flags = XDF_NEED_MINIMAL;
|
||||
if (ignorews)
|
||||
diff_params.flags |= XDF_IGNORE_WHITESPACE;
|
||||
emit_params.ctxlen = context > 0 ? context : 3;
|
||||
emit_params.flags = XDL_EMIT_FUNCNAMES;
|
||||
emit_cb.out_line = filediff_cb;
|
||||
emit_cb.priv = fn;
|
||||
xdl_diff(&file1, &file2, &diff_params, &emit_params, &emit_cb);
|
||||
if (file1.size)
|
||||
free(file1.ptr);
|
||||
if (file2.size)
|
||||
free(file2.ptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cgit_diff_tree(const struct object_id *old_oid,
|
||||
const struct object_id *new_oid,
|
||||
filepair_fn fn, const char *prefix, int ignorews)
|
||||
{
|
||||
struct diff_options opt;
|
||||
struct pathspec_item item;
|
||||
|
||||
memset(&item, 0, sizeof(item));
|
||||
diff_setup(&opt);
|
||||
opt.output_format = DIFF_FORMAT_CALLBACK;
|
||||
opt.detect_rename = 1;
|
||||
opt.rename_limit = ctx.cfg.renamelimit;
|
||||
opt.flags.recursive = 1;
|
||||
if (ignorews)
|
||||
DIFF_XDL_SET(&opt, IGNORE_WHITESPACE);
|
||||
opt.format_callback = cgit_diff_tree_cb;
|
||||
opt.format_callback_data = fn;
|
||||
if (prefix) {
|
||||
item.match = xstrdup(prefix);
|
||||
item.len = strlen(prefix);
|
||||
opt.pathspec.nr = 1;
|
||||
opt.pathspec.items = &item;
|
||||
}
|
||||
diff_setup_done(&opt);
|
||||
|
||||
if (old_oid && !is_null_oid(old_oid))
|
||||
diff_tree_oid(old_oid, new_oid, "", &opt);
|
||||
else
|
||||
diff_root_tree_oid(new_oid, "", &opt);
|
||||
diffcore_std(&opt);
|
||||
diff_flush(&opt);
|
||||
|
||||
free(item.match);
|
||||
}
|
||||
|
||||
void cgit_diff_commit(struct commit *commit, filepair_fn fn, const char *prefix)
|
||||
{
|
||||
const struct object_id *old_oid = NULL;
|
||||
|
||||
if (commit->parents)
|
||||
old_oid = &commit->parents->item->object.oid;
|
||||
cgit_diff_tree(old_oid, &commit->object.oid, fn, prefix,
|
||||
ctx.qry.ignorews);
|
||||
}
|
||||
|
||||
int cgit_parse_snapshots_mask(const char *str)
|
||||
{
|
||||
struct string_list tokens = STRING_LIST_INIT_DUP;
|
||||
struct string_list_item *item;
|
||||
const struct cgit_snapshot_format *f;
|
||||
int rv = 0;
|
||||
|
||||
/* favor legacy setting */
|
||||
if (atoi(str))
|
||||
return 1;
|
||||
|
||||
if (strcmp(str, "all") == 0)
|
||||
return INT_MAX;
|
||||
|
||||
string_list_split(&tokens, str, ' ', -1);
|
||||
string_list_remove_empty_items(&tokens, 0);
|
||||
|
||||
for_each_string_list_item(item, &tokens) {
|
||||
for (f = cgit_snapshot_formats; f->suffix; f++) {
|
||||
if (!strcmp(item->string, f->suffix) ||
|
||||
!strcmp(item->string, f->suffix + 1)) {
|
||||
rv |= cgit_snapshot_format_bit(f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
string_list_clear(&tokens, 0);
|
||||
return rv;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
char * name;
|
||||
char * value;
|
||||
} cgit_env_var;
|
||||
|
||||
void cgit_prepare_repo_env(struct cgit_repo * repo)
|
||||
{
|
||||
cgit_env_var env_vars[] = {
|
||||
{ .name = "CGIT_REPO_URL", .value = repo->url },
|
||||
{ .name = "CGIT_REPO_NAME", .value = repo->name },
|
||||
{ .name = "CGIT_REPO_PATH", .value = repo->path },
|
||||
{ .name = "CGIT_REPO_OWNER", .value = repo->owner },
|
||||
{ .name = "CGIT_REPO_DEFBRANCH", .value = repo->defbranch },
|
||||
{ .name = "CGIT_REPO_SECTION", .value = repo->section },
|
||||
{ .name = "CGIT_REPO_CLONE_URL", .value = repo->clone_url }
|
||||
};
|
||||
int env_var_count = ARRAY_SIZE(env_vars);
|
||||
cgit_env_var *p, *q;
|
||||
static char *warn = "cgit warning: failed to set env: %s=%s\n";
|
||||
|
||||
p = env_vars;
|
||||
q = p + env_var_count;
|
||||
for (; p < q; p++)
|
||||
if (p->value && setenv(p->name, p->value, 1))
|
||||
fprintf(stderr, warn, p->name, p->value);
|
||||
}
|
||||
|
||||
/* Read the content of the specified file into a newly allocated buffer,
|
||||
* zeroterminate the buffer and return 0 on success, errno otherwise.
|
||||
*/
|
||||
int readfile(const char *path, char **buf, size_t *size)
|
||||
{
|
||||
int fd, e;
|
||||
struct stat st;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd == -1)
|
||||
return errno;
|
||||
if (fstat(fd, &st)) {
|
||||
e = errno;
|
||||
close(fd);
|
||||
return e;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
close(fd);
|
||||
return EISDIR;
|
||||
}
|
||||
*buf = xmalloc(st.st_size + 1);
|
||||
*size = read_in_full(fd, *buf, st.st_size);
|
||||
e = errno;
|
||||
(*buf)[*size] = '\0';
|
||||
close(fd);
|
||||
return (*size == st.st_size ? 0 : e);
|
||||
}
|
||||
|
||||
static int is_token_char(char c)
|
||||
{
|
||||
return isalnum(c) || c == '_';
|
||||
}
|
||||
|
||||
/* Replace name with getenv(name), return pointer to zero-terminating char
|
||||
*/
|
||||
static char *expand_macro(char *name, int maxlength)
|
||||
{
|
||||
char *value;
|
||||
size_t len;
|
||||
|
||||
len = 0;
|
||||
value = getenv(name);
|
||||
if (value) {
|
||||
len = strlen(value) + 1;
|
||||
if (len > maxlength)
|
||||
len = maxlength;
|
||||
strlcpy(name, value, len);
|
||||
--len;
|
||||
}
|
||||
return name + len;
|
||||
}
|
||||
|
||||
#define EXPBUFSIZE (1024 * 8)
|
||||
|
||||
/* Replace all tokens prefixed by '$' in the specified text with the
|
||||
* value of the named environment variable.
|
||||
* NB: the return value is a static buffer, i.e. it must be strdup'd
|
||||
* by the caller.
|
||||
*/
|
||||
char *expand_macros(const char *txt)
|
||||
{
|
||||
static char result[EXPBUFSIZE];
|
||||
char *p, *start;
|
||||
int len;
|
||||
|
||||
p = result;
|
||||
start = NULL;
|
||||
while (p < result + EXPBUFSIZE - 1 && txt && *txt) {
|
||||
*p = *txt;
|
||||
if (start) {
|
||||
if (!is_token_char(*txt)) {
|
||||
if (p - start > 0) {
|
||||
*p = '\0';
|
||||
len = result + EXPBUFSIZE - start - 1;
|
||||
p = expand_macro(start, len) - 1;
|
||||
}
|
||||
start = NULL;
|
||||
txt--;
|
||||
}
|
||||
p++;
|
||||
txt++;
|
||||
continue;
|
||||
}
|
||||
if (*txt == '$') {
|
||||
start = p;
|
||||
txt++;
|
||||
continue;
|
||||
}
|
||||
p++;
|
||||
txt++;
|
||||
}
|
||||
*p = '\0';
|
||||
if (start && p - start > 0) {
|
||||
len = result + EXPBUFSIZE - start - 1;
|
||||
p = expand_macro(start, len);
|
||||
*p = '\0';
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
char *get_mimetype_for_filename(const char *filename)
|
||||
{
|
||||
char *ext, *mimetype, *token, line[1024], *saveptr;
|
||||
FILE *file;
|
||||
struct string_list_item *mime;
|
||||
|
||||
if (!filename)
|
||||
return NULL;
|
||||
|
||||
ext = strrchr(filename, '.');
|
||||
if (!ext)
|
||||
return NULL;
|
||||
++ext;
|
||||
if (!ext[0])
|
||||
return NULL;
|
||||
mime = string_list_lookup(&ctx.cfg.mimetypes, ext);
|
||||
if (mime)
|
||||
return xstrdup(mime->util);
|
||||
|
||||
if (!ctx.cfg.mimetype_file)
|
||||
return NULL;
|
||||
file = fopen(ctx.cfg.mimetype_file, "r");
|
||||
if (!file)
|
||||
return NULL;
|
||||
while (fgets(line, sizeof(line), file)) {
|
||||
if (!line[0] || line[0] == '#')
|
||||
continue;
|
||||
mimetype = strtok_r(line, " \t\r\n", &saveptr);
|
||||
while ((token = strtok_r(NULL, " \t\r\n", &saveptr))) {
|
||||
if (!strcasecmp(ext, token)) {
|
||||
fclose(file);
|
||||
return xstrdup(mimetype);
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(file);
|
||||
return NULL;
|
||||
}
|
2
tests/.gitignore
vendored
Normal file
2
tests/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
trash\ directory.t*
|
||||
test-results
|
17
tests/Makefile
Normal file
17
tests/Makefile
Normal file
|
@ -0,0 +1,17 @@
|
|||
include ../git/config.mak.uname
|
||||
-include ../cgit.conf
|
||||
|
||||
SHELL_PATH ?= $(SHELL)
|
||||
SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
|
||||
|
||||
T = $(wildcard t[0-9][0-9][0-9][0-9]-*.sh)
|
||||
|
||||
all: $(T)
|
||||
|
||||
$(T):
|
||||
@'$(SHELL_PATH_SQ)' $@ $(CGIT_TEST_OPTS)
|
||||
|
||||
clean:
|
||||
$(RM) -rf trash
|
||||
|
||||
.PHONY: $(T) clean
|
17
tests/filters/dump.lua
Normal file
17
tests/filters/dump.lua
Normal file
|
@ -0,0 +1,17 @@
|
|||
function filter_open(...)
|
||||
buffer = ""
|
||||
for i = 1, select("#", ...) do
|
||||
buffer = buffer .. select(i, ...) .. " "
|
||||
end
|
||||
end
|
||||
|
||||
function filter_close()
|
||||
html(buffer)
|
||||
return 0
|
||||
end
|
||||
|
||||
function filter_write(str)
|
||||
buffer = buffer .. string.upper(str)
|
||||
end
|
||||
|
||||
|
4
tests/filters/dump.sh
Executable file
4
tests/filters/dump.sh
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/bin/sh
|
||||
|
||||
[ "$#" -gt 0 ] && printf "%s " "$*"
|
||||
tr '[:lower:]' '[:upper:]'
|
176
tests/setup.sh
Executable file
176
tests/setup.sh
Executable file
|
@ -0,0 +1,176 @@
|
|||
# This file should be sourced by all test-scripts
|
||||
#
|
||||
# Main functions:
|
||||
# prepare_tests(description) - setup for testing, i.e. create repos+config
|
||||
# run_test(description, script) - run one test, i.e. eval script
|
||||
#
|
||||
# Helper functions
|
||||
# cgit_query(querystring) - call cgit with the specified querystring
|
||||
# cgit_url(url) - call cgit with the specified virtual url
|
||||
#
|
||||
# Example script:
|
||||
#
|
||||
# . setup.sh
|
||||
# prepare_tests "html validation"
|
||||
# run_test 'repo index' 'cgit_url "/" | tidy -e'
|
||||
# run_test 'repo summary' 'cgit_url "/foo" | tidy -e'
|
||||
|
||||
# We don't want to run Git commands through Valgrind, so we filter out the
|
||||
# --valgrind option here and handle it ourselves. We copy the arguments
|
||||
# assuming that none contain a newline, although other whitespace is
|
||||
# preserved.
|
||||
LF='
|
||||
'
|
||||
test_argv=
|
||||
|
||||
while test $# != 0
|
||||
do
|
||||
case "$1" in
|
||||
--va|--val|--valg|--valgr|--valgri|--valgrin|--valgrind)
|
||||
cgit_valgrind=t
|
||||
test_argv="$test_argv${LF}--verbose"
|
||||
;;
|
||||
*)
|
||||
test_argv="$test_argv$LF$1"
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
OLDIFS=$IFS
|
||||
IFS=$LF
|
||||
set -- $test_argv
|
||||
IFS=$OLDIFS
|
||||
|
||||
: ${TEST_DIRECTORY=$(pwd)/../git/t}
|
||||
: ${TEST_OUTPUT_DIRECTORY=$(pwd)}
|
||||
TEST_NO_CREATE_REPO=YesPlease
|
||||
. "$TEST_DIRECTORY"/test-lib.sh
|
||||
|
||||
# Prepend the directory containing cgit to PATH.
|
||||
if test -n "$cgit_valgrind"
|
||||
then
|
||||
GIT_VALGRIND="$TEST_DIRECTORY/valgrind"
|
||||
CGIT_VALGRIND=$(cd ../valgrind && pwd)
|
||||
PATH="$CGIT_VALGRIND/bin:$PATH"
|
||||
export GIT_VALGRIND CGIT_VALGRIND
|
||||
else
|
||||
PATH="$(pwd)/../..:$PATH"
|
||||
fi
|
||||
|
||||
FILTER_DIRECTORY=$(cd ../filters && pwd)
|
||||
|
||||
if cgit --version | grep -F -q "[+] Lua scripting"; then
|
||||
export CGIT_HAS_LUA=1
|
||||
else
|
||||
export CGIT_HAS_LUA=0
|
||||
fi
|
||||
|
||||
mkrepo() {
|
||||
name=$1
|
||||
count=$2
|
||||
test_create_repo "$name"
|
||||
(
|
||||
cd "$name"
|
||||
n=1
|
||||
while test $n -le $count
|
||||
do
|
||||
echo $n >file-$n
|
||||
git add file-$n
|
||||
git commit -m "commit $n"
|
||||
n=$(expr $n + 1)
|
||||
done
|
||||
if test "$3" = "testplus"
|
||||
then
|
||||
echo "hello" >a+b
|
||||
git add a+b
|
||||
git commit -m "add a+b"
|
||||
git branch "1+2"
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
setup_repos()
|
||||
{
|
||||
rm -rf cache
|
||||
mkdir -p cache
|
||||
mkrepo repos/foo 5 >/dev/null
|
||||
mkrepo repos/bar 50 >/dev/null
|
||||
mkrepo repos/foo+bar 10 testplus >/dev/null
|
||||
mkrepo "repos/with space" 2 >/dev/null
|
||||
mkrepo repos/filter 5 testplus >/dev/null
|
||||
cat >cgitrc <<EOF
|
||||
virtual-root=/
|
||||
cache-root=$PWD/cache
|
||||
|
||||
cache-size=1021
|
||||
snapshots=tar.gz tar.bz zip
|
||||
enable-log-filecount=1
|
||||
enable-log-linecount=1
|
||||
summary-log=5
|
||||
summary-branches=5
|
||||
summary-tags=5
|
||||
clone-url=git://example.org/\$CGIT_REPO_URL.git
|
||||
enable-filter-overrides=1
|
||||
|
||||
repo.url=foo
|
||||
repo.path=$PWD/repos/foo/.git
|
||||
# Do not specify a description for this repo, as it then will be assigned
|
||||
# the constant value "[no description]" (which actually used to cause a
|
||||
# segfault).
|
||||
|
||||
repo.url=bar
|
||||
repo.path=$PWD/repos/bar/.git
|
||||
repo.desc=the bar repo
|
||||
|
||||
repo.url=foo+bar
|
||||
repo.path=$PWD/repos/foo+bar/.git
|
||||
repo.desc=the foo+bar repo
|
||||
|
||||
repo.url=with space
|
||||
repo.path=$PWD/repos/with space/.git
|
||||
repo.desc=spaced repo
|
||||
|
||||
repo.url=filter-exec
|
||||
repo.path=$PWD/repos/filter/.git
|
||||
repo.desc=filtered repo
|
||||
repo.about-filter=exec:$FILTER_DIRECTORY/dump.sh
|
||||
repo.commit-filter=exec:$FILTER_DIRECTORY/dump.sh
|
||||
repo.email-filter=exec:$FILTER_DIRECTORY/dump.sh
|
||||
repo.source-filter=exec:$FILTER_DIRECTORY/dump.sh
|
||||
repo.readme=master:a+b
|
||||
EOF
|
||||
|
||||
if [ $CGIT_HAS_LUA -eq 1 ]; then
|
||||
cat >>cgitrc <<EOF
|
||||
repo.url=filter-lua
|
||||
repo.path=$PWD/repos/filter/.git
|
||||
repo.desc=filtered repo
|
||||
repo.about-filter=lua:$FILTER_DIRECTORY/dump.lua
|
||||
repo.commit-filter=lua:$FILTER_DIRECTORY/dump.lua
|
||||
repo.email-filter=lua:$FILTER_DIRECTORY/dump.lua
|
||||
repo.source-filter=lua:$FILTER_DIRECTORY/dump.lua
|
||||
repo.readme=master:a+b
|
||||
EOF
|
||||
fi
|
||||
}
|
||||
|
||||
cgit_query()
|
||||
{
|
||||
CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="$1" cgit
|
||||
}
|
||||
|
||||
cgit_url()
|
||||
{
|
||||
CGIT_CONFIG="$PWD/cgitrc" QUERY_STRING="url=$1" cgit
|
||||
}
|
||||
|
||||
strip_headers() {
|
||||
while read -r line
|
||||
do
|
||||
test -z "$line" && break
|
||||
done
|
||||
cat
|
||||
}
|
||||
|
||||
test -z "$CGIT_TEST_NO_CREATE_REPOS" && setup_repos
|
41
tests/t0001-validate-git-versions.sh
Executable file
41
tests/t0001-validate-git-versions.sh
Executable file
|
@ -0,0 +1,41 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check Git version is correct'
|
||||
CGIT_TEST_NO_CREATE_REPOS=YesPlease
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'extract Git version from Makefile' '
|
||||
sed -n -e "/^GIT_VER[ ]*=/ {
|
||||
s/^GIT_VER[ ]*=[ ]*//
|
||||
p
|
||||
}" ../../Makefile >makefile_version
|
||||
'
|
||||
|
||||
# Note that Git's GIT-VERSION-GEN script applies "s/-/./g" to the version
|
||||
# string to produce the internal version in the GIT-VERSION-FILE, so we
|
||||
# must apply the same transformation to the version in the Makefile before
|
||||
# comparing them.
|
||||
test_expect_success 'test Git version matches Makefile' '
|
||||
( cat ../../git/GIT-VERSION-FILE || echo "No GIT-VERSION-FILE" ) |
|
||||
sed -e "s/GIT_VERSION[ ]*=[ ]*//" -e "s/\\.dirty$//" >git_version &&
|
||||
sed -e "s/-/./g" makefile_version >makefile_git_version &&
|
||||
test_cmp git_version makefile_git_version
|
||||
'
|
||||
|
||||
test_expect_success 'test submodule version matches Makefile' '
|
||||
if ! test -e ../../git/.git
|
||||
then
|
||||
echo "git/ is not a Git repository" >&2
|
||||
else
|
||||
(
|
||||
cd ../.. &&
|
||||
sm_sha1=$(git ls-files --stage -- git |
|
||||
sed -e "s/^[0-9]* \\([0-9a-f]*\\) [0-9] .*$/\\1/") &&
|
||||
cd git &&
|
||||
git describe --match "v[0-9]*" $sm_sha1
|
||||
) | sed -e "s/^v//" -e "s/-/./" >sm_version &&
|
||||
test_cmp sm_version makefile_version
|
||||
fi
|
||||
'
|
||||
|
||||
test_done
|
40
tests/t0010-validate-html.sh
Executable file
40
tests/t0010-validate-html.sh
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Validate html with tidy'
|
||||
. ./setup.sh
|
||||
|
||||
|
||||
test_url()
|
||||
{
|
||||
tidy_opt="-eq"
|
||||
test -z "$NO_TIDY_WARNINGS" || tidy_opt+=" --show-warnings no"
|
||||
cgit_url "$1" >tidy-$test_count.tmp || return
|
||||
sed -e "1,4d" tidy-$test_count.tmp >tidy-$test_count || return
|
||||
"$tidy" $tidy_opt tidy-$test_count
|
||||
rc=$?
|
||||
|
||||
# tidy returns with exitcode 1 on warnings, 2 on error
|
||||
if test $rc = 2
|
||||
then
|
||||
false
|
||||
else
|
||||
:
|
||||
fi
|
||||
}
|
||||
|
||||
tidy=`which tidy 2>/dev/null`
|
||||
test -n "$tidy" || {
|
||||
skip_all='Skipping html validation tests: tidy not found'
|
||||
test_done
|
||||
exit
|
||||
}
|
||||
|
||||
test_expect_success 'index page' 'test_url ""'
|
||||
test_expect_success 'foo' 'test_url "foo"'
|
||||
test_expect_success 'foo/log' 'test_url "foo/log"'
|
||||
test_expect_success 'foo/tree' 'test_url "foo/tree"'
|
||||
test_expect_success 'foo/tree/file-1' 'test_url "foo/tree/file-1"'
|
||||
test_expect_success 'foo/commit' 'test_url "foo/commit"'
|
||||
test_expect_success 'foo/diff' 'test_url "foo/diff"'
|
||||
|
||||
test_done
|
78
tests/t0020-validate-cache.sh
Executable file
78
tests/t0020-validate-cache.sh
Executable file
|
@ -0,0 +1,78 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Validate cache'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'verify cache-size=0' '
|
||||
|
||||
rm -f cache/* &&
|
||||
sed -e "s/cache-size=1021$/cache-size=0/" cgitrc >cgitrc.tmp &&
|
||||
mv -f cgitrc.tmp cgitrc &&
|
||||
cgit_url "" &&
|
||||
cgit_url "foo" &&
|
||||
cgit_url "foo/refs" &&
|
||||
cgit_url "foo/tree" &&
|
||||
cgit_url "foo/log" &&
|
||||
cgit_url "foo/diff" &&
|
||||
cgit_url "foo/patch" &&
|
||||
cgit_url "bar" &&
|
||||
cgit_url "bar/refs" &&
|
||||
cgit_url "bar/tree" &&
|
||||
cgit_url "bar/log" &&
|
||||
cgit_url "bar/diff" &&
|
||||
cgit_url "bar/patch" &&
|
||||
ls cache >output &&
|
||||
test_line_count = 0 output
|
||||
'
|
||||
|
||||
test_expect_success 'verify cache-size=1' '
|
||||
|
||||
rm -f cache/* &&
|
||||
sed -e "s/cache-size=0$/cache-size=1/" cgitrc >cgitrc.tmp &&
|
||||
mv -f cgitrc.tmp cgitrc &&
|
||||
cgit_url "" &&
|
||||
cgit_url "foo" &&
|
||||
cgit_url "foo/refs" &&
|
||||
cgit_url "foo/tree" &&
|
||||
cgit_url "foo/log" &&
|
||||
cgit_url "foo/diff" &&
|
||||
cgit_url "foo/patch" &&
|
||||
cgit_url "bar" &&
|
||||
cgit_url "bar/refs" &&
|
||||
cgit_url "bar/tree" &&
|
||||
cgit_url "bar/log" &&
|
||||
cgit_url "bar/diff" &&
|
||||
cgit_url "bar/patch" &&
|
||||
ls cache >output &&
|
||||
test_line_count = 1 output
|
||||
'
|
||||
|
||||
test_expect_success 'verify cache-size=1021' '
|
||||
|
||||
rm -f cache/* &&
|
||||
sed -e "s/cache-size=1$/cache-size=1021/" cgitrc >cgitrc.tmp &&
|
||||
mv -f cgitrc.tmp cgitrc &&
|
||||
cgit_url "" &&
|
||||
cgit_url "foo" &&
|
||||
cgit_url "foo/refs" &&
|
||||
cgit_url "foo/tree" &&
|
||||
cgit_url "foo/log" &&
|
||||
cgit_url "foo/diff" &&
|
||||
cgit_url "foo/patch" &&
|
||||
cgit_url "bar" &&
|
||||
cgit_url "bar/refs" &&
|
||||
cgit_url "bar/tree" &&
|
||||
cgit_url "bar/log" &&
|
||||
cgit_url "bar/diff" &&
|
||||
cgit_url "bar/patch" &&
|
||||
ls cache >output &&
|
||||
test_line_count = 13 output &&
|
||||
cgit_url "foo/ls_cache" >output.full &&
|
||||
strip_headers <output.full >output &&
|
||||
test_line_count = 13 output &&
|
||||
# Check that ls_cache output is cached correctly
|
||||
cgit_url "foo/ls_cache" >output.second &&
|
||||
test_cmp output.full output.second
|
||||
'
|
||||
|
||||
test_done
|
17
tests/t0101-index.sh
Executable file
17
tests/t0101-index.sh
Executable file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on index page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate index page' 'cgit_url "" >tmp'
|
||||
test_expect_success 'find foo repo' 'grep "foo" tmp'
|
||||
test_expect_success 'find foo description' 'grep "\[no description\]" tmp'
|
||||
test_expect_success 'find bar repo' 'grep "bar" tmp'
|
||||
test_expect_success 'find bar description' 'grep "the bar repo" tmp'
|
||||
test_expect_success 'find foo+bar repo' 'grep ">foo+bar<" tmp'
|
||||
test_expect_success 'verify foo+bar link' 'grep "/foo+bar/" tmp'
|
||||
test_expect_success 'verify "with%20space" link' 'grep "/with%20space/" tmp'
|
||||
test_expect_success 'no tree-link' '! grep "foo/tree" tmp'
|
||||
test_expect_success 'no log-link' '! grep "foo/log" tmp'
|
||||
|
||||
test_done
|
25
tests/t0102-summary.sh
Executable file
25
tests/t0102-summary.sh
Executable file
|
@ -0,0 +1,25 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on summary page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate foo summary' 'cgit_url "foo" >tmp'
|
||||
test_expect_success 'find commit 1' 'grep "commit 1" tmp'
|
||||
test_expect_success 'find commit 5' 'grep "commit 5" tmp'
|
||||
test_expect_success 'find branch master' 'grep "master" tmp'
|
||||
test_expect_success 'no tags' '! grep "tags" tmp'
|
||||
test_expect_success 'clone-url expanded correctly' '
|
||||
grep "git://example.org/foo.git" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'generate bar summary' 'cgit_url "bar" >tmp'
|
||||
test_expect_success 'no commit 45' '! grep "commit 45" tmp'
|
||||
test_expect_success 'find commit 46' 'grep "commit 46" tmp'
|
||||
test_expect_success 'find commit 50' 'grep "commit 50" tmp'
|
||||
test_expect_success 'find branch master' 'grep "master" tmp'
|
||||
test_expect_success 'no tags' '! grep "tags" tmp'
|
||||
test_expect_success 'clone-url expanded correctly' '
|
||||
grep "git://example.org/bar.git" tmp
|
||||
'
|
||||
|
||||
test_done
|
24
tests/t0103-log.sh
Executable file
24
tests/t0103-log.sh
Executable file
|
@ -0,0 +1,24 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on log page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate foo/log' 'cgit_url "foo/log" >tmp'
|
||||
test_expect_success 'find commit 1' 'grep "commit 1" tmp'
|
||||
test_expect_success 'find commit 5' 'grep "commit 5" tmp'
|
||||
|
||||
test_expect_success 'generate bar/log' 'cgit_url "bar/log" >tmp'
|
||||
test_expect_success 'find commit 1' 'grep "commit 1" tmp'
|
||||
test_expect_success 'find commit 50' 'grep "commit 50" tmp'
|
||||
|
||||
test_expect_success 'generate "with%20space/log?qt=grep&q=commit+1"' '
|
||||
cgit_url "with+space/log&qt=grep&q=commit+1" >tmp
|
||||
'
|
||||
test_expect_success 'find commit 1' 'grep "commit 1" tmp'
|
||||
test_expect_success 'find link with %20 in path' 'grep "/with%20space/log/?qt=grep" tmp'
|
||||
test_expect_success 'find link with + in arg' 'grep "/log/?qt=grep&q=commit+1" tmp'
|
||||
test_expect_success 'no links with space in path' '! grep "href=./with space/" tmp'
|
||||
test_expect_success 'no links with space in arg' '! grep "q=commit 1" tmp'
|
||||
test_expect_success 'commit 2 is not visible' '! grep "commit 2" tmp'
|
||||
|
||||
test_done
|
32
tests/t0104-tree.sh
Executable file
32
tests/t0104-tree.sh
Executable file
|
@ -0,0 +1,32 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on tree page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate bar/tree' 'cgit_url "bar/tree" >tmp'
|
||||
test_expect_success 'find file-1' 'grep "file-1" tmp'
|
||||
test_expect_success 'find file-50' 'grep "file-50" tmp'
|
||||
|
||||
test_expect_success 'generate bar/tree/file-50' 'cgit_url "bar/tree/file-50" >tmp'
|
||||
|
||||
test_expect_success 'find line 1' '
|
||||
grep "<a id=.n1. href=.#n1.>1</a>" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'no line 2' '
|
||||
! grep "<a id=.n2. href=.#n2.>2</a>" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'generate foo+bar/tree' 'cgit_url "foo%2bbar/tree" >tmp'
|
||||
|
||||
test_expect_success 'verify a+b link' '
|
||||
grep "/foo+bar/tree/a+b" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'generate foo+bar/tree?h=1+2' 'cgit_url "foo%2bbar/tree&h=1%2b2" >tmp'
|
||||
|
||||
test_expect_success 'verify a+b?h=1+2 link' '
|
||||
grep "/foo+bar/tree/a+b?h=1%2b2" tmp
|
||||
'
|
||||
|
||||
test_done
|
36
tests/t0105-commit.sh
Executable file
36
tests/t0105-commit.sh
Executable file
|
@ -0,0 +1,36 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on commit page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate foo/commit' 'cgit_url "foo/commit" >tmp'
|
||||
test_expect_success 'find tree link' 'grep "<a href=./foo/tree/.>" tmp'
|
||||
test_expect_success 'find parent link' 'grep -E "<a href=./foo/commit/\?id=.+>" tmp'
|
||||
|
||||
test_expect_success 'find commit subject' '
|
||||
grep "<div class=.commit-subject.>commit 5<" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find commit msg' 'grep "<div class=.commit-msg.></div>" tmp'
|
||||
test_expect_success 'find diffstat' 'grep "<table summary=.diffstat. class=.diffstat.>" tmp'
|
||||
|
||||
test_expect_success 'find diff summary' '
|
||||
grep "1 files changed, 1 insertions, 0 deletions" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'get root commit' '
|
||||
root=$(cd repos/foo && git rev-list --reverse HEAD | head -1) &&
|
||||
cgit_url "foo/commit&id=$root" >tmp &&
|
||||
grep "</html>" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'root commit contains diffstat' '
|
||||
grep "<a href=./foo/diff/file-1.id=[0-9a-f]\{40\}.>file-1</a>" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'root commit contains diff' '
|
||||
grep ">diff --git a/file-1 b/file-1<" tmp &&
|
||||
grep "<div class=.add.>+1</div>" tmp
|
||||
'
|
||||
|
||||
test_done
|
19
tests/t0106-diff.sh
Executable file
19
tests/t0106-diff.sh
Executable file
|
@ -0,0 +1,19 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on diff page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate foo/diff' 'cgit_url "foo/diff" >tmp'
|
||||
test_expect_success 'find diff header' 'grep "a/file-5 b/file-5" tmp'
|
||||
test_expect_success 'find blob link' 'grep "<a href=./foo/tree/file-5?id=" tmp'
|
||||
test_expect_success 'find added file' 'grep "new file mode 100644" tmp'
|
||||
|
||||
test_expect_success 'find hunk header' '
|
||||
grep "<div class=.hunk.>@@ -0,0 +1 @@</div>" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find added line' '
|
||||
grep "<div class=.add.>+5</div>" tmp
|
||||
'
|
||||
|
||||
test_done
|
82
tests/t0107-snapshot.sh
Executable file
82
tests/t0107-snapshot.sh
Executable file
|
@ -0,0 +1,82 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Verify snapshot'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'get foo/snapshot/master.tar.gz' '
|
||||
cgit_url "foo/snapshot/master.tar.gz" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'check html headers' '
|
||||
head -n 1 tmp |
|
||||
grep "Content-Type: application/x-gzip" &&
|
||||
|
||||
head -n 2 tmp |
|
||||
grep "Content-Disposition: inline; filename=.master.tar.gz."
|
||||
'
|
||||
|
||||
test_expect_success 'strip off the header lines' '
|
||||
strip_headers <tmp >master.tar.gz
|
||||
'
|
||||
|
||||
test_expect_success 'verify gzip format' '
|
||||
gunzip --test master.tar.gz
|
||||
'
|
||||
|
||||
test_expect_success 'untar' '
|
||||
rm -rf master &&
|
||||
tar -xzf master.tar.gz
|
||||
'
|
||||
|
||||
test_expect_success 'count files' '
|
||||
ls master/ >output &&
|
||||
test_line_count = 5 output
|
||||
'
|
||||
|
||||
test_expect_success 'verify untarred file-5' '
|
||||
grep "^5$" master/file-5 &&
|
||||
test_line_count = 1 master/file-5
|
||||
'
|
||||
|
||||
test_expect_success 'get foo/snapshot/master.zip' '
|
||||
cgit_url "foo/snapshot/master.zip" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'check HTML headers (zip)' '
|
||||
head -n 1 tmp |
|
||||
grep "Content-Type: application/x-zip" &&
|
||||
|
||||
head -n 2 tmp |
|
||||
grep "Content-Disposition: inline; filename=.master.zip."
|
||||
'
|
||||
|
||||
test_expect_success 'strip off the header lines (zip)' '
|
||||
strip_headers <tmp >master.zip
|
||||
'
|
||||
|
||||
if test -n "$(which unzip 2>/dev/null)"; then
|
||||
test_set_prereq UNZIP
|
||||
else
|
||||
say 'Skipping ZIP validation tests: unzip not found'
|
||||
fi
|
||||
|
||||
test_expect_success UNZIP 'verify zip format' '
|
||||
unzip -t master.zip
|
||||
'
|
||||
|
||||
test_expect_success UNZIP 'unzip' '
|
||||
rm -rf master &&
|
||||
unzip master.zip
|
||||
'
|
||||
|
||||
test_expect_success UNZIP 'count files (zip)' '
|
||||
ls master/ >output &&
|
||||
test_line_count = 5 output
|
||||
'
|
||||
|
||||
test_expect_success UNZIP 'verify unzipped file-5' '
|
||||
grep "^5$" master/file-5 &&
|
||||
test_line_count = 1 master/file-5
|
||||
'
|
||||
|
||||
test_done
|
62
tests/t0108-patch.sh
Executable file
62
tests/t0108-patch.sh
Executable file
|
@ -0,0 +1,62 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on patch page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate foo/patch' '
|
||||
cgit_query "url=foo/patch" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find `From:` line' '
|
||||
grep "^From: " tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find `Date:` line' '
|
||||
grep "^Date: " tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find `Subject:` line' '
|
||||
grep "^Subject: commit 5" tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find `cgit` signature' '
|
||||
tail -2 tmp | head -1 | grep "^cgit"
|
||||
'
|
||||
|
||||
test_expect_success 'compare with output of git-format-patch(1)' '
|
||||
CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) &&
|
||||
git --git-dir="$PWD/repos/foo/.git" format-patch --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD^ >tmp2 &&
|
||||
strip_headers <tmp >tmp_ &&
|
||||
test_cmp tmp_ tmp2
|
||||
'
|
||||
|
||||
test_expect_success 'find initial commit' '
|
||||
root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD)
|
||||
'
|
||||
|
||||
test_expect_success 'generate patch for initial commit' '
|
||||
cgit_query "url=foo/patch&id=$root" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find `cgit` signature' '
|
||||
tail -2 tmp | head -1 | grep "^cgit"
|
||||
'
|
||||
|
||||
test_expect_success 'generate patches for multiple commits' '
|
||||
id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) &&
|
||||
id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) &&
|
||||
cgit_query "url=foo/patch&id=$id&id2=$id2" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'find `cgit` signature' '
|
||||
tail -2 tmp | head -1 | grep "^cgit"
|
||||
'
|
||||
|
||||
test_expect_success 'compare with output of git-format-patch(1)' '
|
||||
CGIT_VERSION=$(sed -n "s/CGIT_VERSION = //p" ../../VERSION) &&
|
||||
git --git-dir="$PWD/repos/foo/.git" format-patch -N --subject-prefix="" --signature="cgit $CGIT_VERSION" --stdout HEAD~3..HEAD >tmp2 &&
|
||||
strip_headers <tmp >tmp_ &&
|
||||
test_cmp tmp_ tmp2
|
||||
'
|
||||
|
||||
test_done
|
42
tests/t0109-gitconfig.sh
Executable file
42
tests/t0109-gitconfig.sh
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Ensure that git does not access $HOME'
|
||||
. ./setup.sh
|
||||
|
||||
test -n "$(which strace 2>/dev/null)" || {
|
||||
skip_all='Skipping access validation tests: strace not found'
|
||||
test_done
|
||||
exit
|
||||
}
|
||||
|
||||
test_no_home_access () {
|
||||
non_existent_path="/path/to/some/place/that/does/not/possibly/exist"
|
||||
while test -d "$non_existent_path"; do
|
||||
non_existent_path="$non_existent_path/$(date +%N)"
|
||||
done &&
|
||||
strace \
|
||||
-E HOME="$non_existent_path" \
|
||||
-E CGIT_CONFIG="$PWD/cgitrc" \
|
||||
-E QUERY_STRING="url=$1" \
|
||||
-e access -f -o strace.out cgit &&
|
||||
test_must_fail grep "$non_existent_path" strace.out
|
||||
}
|
||||
|
||||
test_no_home_access_success() {
|
||||
test_expect_success "do not access \$HOME: $1" "
|
||||
test_no_home_access '$1'
|
||||
"
|
||||
}
|
||||
|
||||
test_no_home_access_success
|
||||
test_no_home_access_success foo
|
||||
test_no_home_access_success foo/refs
|
||||
test_no_home_access_success foo/log
|
||||
test_no_home_access_success foo/tree
|
||||
test_no_home_access_success foo/tree/file-1
|
||||
test_no_home_access_success foo/commit
|
||||
test_no_home_access_success foo/diff
|
||||
test_no_home_access_success foo/patch
|
||||
test_no_home_access_success foo/snapshot/master.tar.gz
|
||||
|
||||
test_done
|
42
tests/t0110-rawdiff.sh
Executable file
42
tests/t0110-rawdiff.sh
Executable file
|
@ -0,0 +1,42 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check content on rawdiff page'
|
||||
. ./setup.sh
|
||||
|
||||
test_expect_success 'generate foo/rawdiff' '
|
||||
cgit_query "url=foo/rawdiff" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'compare with output of git-diff(1)' '
|
||||
git --git-dir="$PWD/repos/foo/.git" diff HEAD^.. >tmp2 &&
|
||||
sed "1,4d" tmp >tmp_ &&
|
||||
cmp tmp_ tmp2
|
||||
'
|
||||
|
||||
test_expect_success 'find initial commit' '
|
||||
root=$(git --git-dir="$PWD/repos/foo/.git" rev-list --max-parents=0 HEAD)
|
||||
'
|
||||
|
||||
test_expect_success 'generate diff for initial commit' '
|
||||
cgit_query "url=foo/rawdiff&id=$root" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'compare with output of git-diff-tree(1)' '
|
||||
git --git-dir="$PWD/repos/foo/.git" diff-tree -p --no-commit-id --root "$root" >tmp2 &&
|
||||
sed "1,4d" tmp >tmp_ &&
|
||||
cmp tmp_ tmp2
|
||||
'
|
||||
|
||||
test_expect_success 'generate diff for multiple commits' '
|
||||
id=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD) &&
|
||||
id2=$(git --git-dir="$PWD/repos/foo/.git" rev-parse HEAD~3) &&
|
||||
cgit_query "url=foo/rawdiff&id=$id&id2=$id2" >tmp
|
||||
'
|
||||
|
||||
test_expect_success 'compare with output of git-diff(1)' '
|
||||
git --git-dir="$PWD/repos/foo/.git" diff HEAD~3..HEAD >tmp2 &&
|
||||
sed "1,4d" tmp >tmp_ &&
|
||||
cmp tmp_ tmp2
|
||||
'
|
||||
|
||||
test_done
|
46
tests/t0111-filter.sh
Executable file
46
tests/t0111-filter.sh
Executable file
|
@ -0,0 +1,46 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='Check filtered content'
|
||||
. ./setup.sh
|
||||
|
||||
prefixes="exec"
|
||||
if [ $CGIT_HAS_LUA -eq 1 ]; then
|
||||
prefixes="$prefixes lua"
|
||||
fi
|
||||
|
||||
for prefix in $prefixes
|
||||
do
|
||||
test_expect_success "generate filter-$prefix/tree/a%2bb" "
|
||||
cgit_url 'filter-$prefix/tree/a%2bb' >tmp
|
||||
"
|
||||
|
||||
test_expect_success "check whether the $prefix source filter works" '
|
||||
grep "<code>a+b HELLO$" tmp
|
||||
'
|
||||
|
||||
test_expect_success "generate filter-$prefix/about/" "
|
||||
cgit_url 'filter-$prefix/about/' >tmp
|
||||
"
|
||||
|
||||
test_expect_success "check whether the $prefix about filter works" '
|
||||
grep "<div id='"'"'summary'"'"'>a+b HELLO$" tmp
|
||||
'
|
||||
|
||||
test_expect_success "generate filter-$prefix/commit/" "
|
||||
cgit_url 'filter-$prefix/commit/' >tmp
|
||||
"
|
||||
|
||||
test_expect_success "check whether the $prefix commit filter works" '
|
||||
grep "<div class='"'"'commit-subject'"'"'>ADD A+B" tmp
|
||||
'
|
||||
|
||||
test_expect_success "check whether the $prefix email filter works for authors" '
|
||||
grep "<author@example.com> commit A U THOR <AUTHOR@EXAMPLE.COM>" tmp
|
||||
'
|
||||
|
||||
test_expect_success "check whether the $prefix email filter works for committers" '
|
||||
grep "<committer@example.com> commit C O MITTER <COMMITTER@EXAMPLE.COM>" tmp
|
||||
'
|
||||
done
|
||||
|
||||
test_done
|
12
tests/valgrind/bin/cgit
Executable file
12
tests/valgrind/bin/cgit
Executable file
|
@ -0,0 +1,12 @@
|
|||
#!/bin/sh
|
||||
|
||||
# Note that we currently use Git's suppression file and there are variables
|
||||
# $GIT_VALGRIND and $CGIT_VALGRIND which point to different places.
|
||||
exec valgrind -q --error-exitcode=126 \
|
||||
--suppressions="$GIT_VALGRIND/default.supp" \
|
||||
--gen-suppressions=all \
|
||||
--leak-check=no \
|
||||
--track-origins=yes \
|
||||
--log-fd=4 \
|
||||
--input-fd=4 \
|
||||
"$CGIT_VALGRIND/../../cgit" "$@"
|
149
ui-atom.c
Normal file
149
ui-atom.c
Normal file
|
@ -0,0 +1,149 @@
|
|||
/* ui-atom.c: functions for atom feeds
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-atom.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
static void add_entry(struct commit *commit, const char *host)
|
||||
{
|
||||
char delim = '&';
|
||||
char *hex;
|
||||
char *mail, *t, *t2;
|
||||
struct commitinfo *info;
|
||||
|
||||
info = cgit_parse_commit(commit);
|
||||
hex = oid_to_hex(&commit->object.oid);
|
||||
html("<entry>\n");
|
||||
html("<title>");
|
||||
html_txt(info->subject);
|
||||
html("</title>\n");
|
||||
html("<updated>");
|
||||
html_txt(show_date(info->committer_date, 0,
|
||||
date_mode_from_type(DATE_ISO8601_STRICT)));
|
||||
html("</updated>\n");
|
||||
html("<author>\n");
|
||||
if (info->author) {
|
||||
html("<name>");
|
||||
html_txt(info->author);
|
||||
html("</name>\n");
|
||||
}
|
||||
if (info->author_email && !ctx.cfg.noplainemail) {
|
||||
mail = xstrdup(info->author_email);
|
||||
t = strchr(mail, '<');
|
||||
if (t)
|
||||
t++;
|
||||
else
|
||||
t = mail;
|
||||
t2 = strchr(t, '>');
|
||||
if (t2)
|
||||
*t2 = '\0';
|
||||
html("<email>");
|
||||
html_txt(t);
|
||||
html("</email>\n");
|
||||
free(mail);
|
||||
}
|
||||
html("</author>\n");
|
||||
html("<published>");
|
||||
html_txt(show_date(info->author_date, 0,
|
||||
date_mode_from_type(DATE_ISO8601_STRICT)));
|
||||
html("</published>\n");
|
||||
if (host) {
|
||||
char *pageurl;
|
||||
html("<link rel='alternate' type='text/html' href='");
|
||||
html(cgit_httpscheme());
|
||||
html_attr(host);
|
||||
pageurl = cgit_pageurl(ctx.repo->url, "commit", NULL);
|
||||
html_attr(pageurl);
|
||||
if (ctx.cfg.virtual_root)
|
||||
delim = '?';
|
||||
html_attrf("%cid=%s", delim, hex);
|
||||
html("'/>\n");
|
||||
free(pageurl);
|
||||
}
|
||||
htmlf("<id>%s</id>\n", hex);
|
||||
html("<content type='text'>\n");
|
||||
html_txt(info->msg);
|
||||
html("</content>\n");
|
||||
html("<content type='xhtml'>\n");
|
||||
html("<div xmlns='http://www.w3.org/1999/xhtml'>\n");
|
||||
html("<pre>\n");
|
||||
html_txt(info->msg);
|
||||
html("</pre>\n");
|
||||
html("</div>\n");
|
||||
html("</content>\n");
|
||||
html("</entry>\n");
|
||||
cgit_free_commitinfo(info);
|
||||
}
|
||||
|
||||
|
||||
void cgit_print_atom(char *tip, const char *path, int max_count)
|
||||
{
|
||||
char *host;
|
||||
const char *argv[] = {NULL, tip, NULL, NULL, NULL};
|
||||
struct commit *commit;
|
||||
struct rev_info rev;
|
||||
int argc = 2;
|
||||
|
||||
if (ctx.qry.show_all)
|
||||
argv[1] = "--all";
|
||||
else if (!tip)
|
||||
argv[1] = ctx.qry.head;
|
||||
|
||||
if (path) {
|
||||
argv[argc++] = "--";
|
||||
argv[argc++] = path;
|
||||
}
|
||||
|
||||
init_revisions(&rev, NULL);
|
||||
rev.abbrev = DEFAULT_ABBREV;
|
||||
rev.commit_format = CMIT_FMT_DEFAULT;
|
||||
rev.verbose_header = 1;
|
||||
rev.show_root_diff = 0;
|
||||
rev.max_count = max_count;
|
||||
setup_revisions(argc, argv, &rev, NULL);
|
||||
prepare_revision_walk(&rev);
|
||||
|
||||
host = cgit_hosturl();
|
||||
ctx.page.mimetype = "text/xml";
|
||||
ctx.page.charset = "utf-8";
|
||||
cgit_print_http_headers();
|
||||
html("<feed xmlns='http://www.w3.org/2005/Atom'>\n");
|
||||
html("<title>");
|
||||
html_txt(ctx.repo->name);
|
||||
if (path) {
|
||||
html("/");
|
||||
html_txt(path);
|
||||
}
|
||||
if (tip && !ctx.qry.show_all) {
|
||||
html(", branch ");
|
||||
html_txt(tip);
|
||||
}
|
||||
html("</title>\n");
|
||||
html("<subtitle>");
|
||||
html_txt(ctx.repo->desc);
|
||||
html("</subtitle>\n");
|
||||
if (host) {
|
||||
char *repourl = cgit_repourl(ctx.repo->url);
|
||||
html("<link rel='alternate' type='text/html' href='");
|
||||
html(cgit_httpscheme());
|
||||
html_attr(host);
|
||||
html_attr(repourl);
|
||||
html("'/>\n");
|
||||
free(repourl);
|
||||
}
|
||||
while ((commit = get_revision(&rev)) != NULL) {
|
||||
add_entry(commit, host);
|
||||
free_commit_buffer(the_repository->parsed_objects, commit);
|
||||
free_commit_list(commit->parents);
|
||||
commit->parents = NULL;
|
||||
}
|
||||
html("</feed>\n");
|
||||
free(host);
|
||||
}
|
6
ui-atom.h
Normal file
6
ui-atom.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef UI_ATOM_H
|
||||
#define UI_ATOM_H
|
||||
|
||||
extern void cgit_print_atom(char *tip, const char *path, int max_count);
|
||||
|
||||
#endif
|
302
ui-blame.c
Normal file
302
ui-blame.c
Normal file
|
@ -0,0 +1,302 @@
|
|||
/* ui-blame.c: functions for blame output
|
||||
*
|
||||
* Copyright (C) 2006-2017 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-blame.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
#include "argv-array.h"
|
||||
#include "blame.h"
|
||||
|
||||
|
||||
static char *emit_suspect_detail(struct blame_origin *suspect)
|
||||
{
|
||||
struct commitinfo *info;
|
||||
struct strbuf detail = STRBUF_INIT;
|
||||
|
||||
info = cgit_parse_commit(suspect->commit);
|
||||
|
||||
strbuf_addf(&detail, "author %s", info->author);
|
||||
if (!ctx.cfg.noplainemail)
|
||||
strbuf_addf(&detail, " %s", info->author_email);
|
||||
strbuf_addf(&detail, " %s\n",
|
||||
show_date(info->author_date, info->author_tz,
|
||||
cgit_date_mode(DATE_ISO8601)));
|
||||
|
||||
strbuf_addf(&detail, "committer %s", info->committer);
|
||||
if (!ctx.cfg.noplainemail)
|
||||
strbuf_addf(&detail, " %s", info->committer_email);
|
||||
strbuf_addf(&detail, " %s\n\n",
|
||||
show_date(info->committer_date, info->committer_tz,
|
||||
cgit_date_mode(DATE_ISO8601)));
|
||||
|
||||
strbuf_addstr(&detail, info->subject);
|
||||
|
||||
cgit_free_commitinfo(info);
|
||||
return strbuf_detach(&detail, NULL);
|
||||
}
|
||||
|
||||
static void emit_blame_entry_hash(struct blame_entry *ent)
|
||||
{
|
||||
struct blame_origin *suspect = ent->suspect;
|
||||
struct object_id *oid = &suspect->commit->object.oid;
|
||||
unsigned long line = 0;
|
||||
|
||||
char *detail = emit_suspect_detail(suspect);
|
||||
html("<span class='sha1'>");
|
||||
cgit_commit_link(find_unique_abbrev(oid, DEFAULT_ABBREV), detail,
|
||||
NULL, ctx.qry.head, oid_to_hex(oid), suspect->path);
|
||||
html("</span>");
|
||||
free(detail);
|
||||
|
||||
while (line++ < ent->num_lines)
|
||||
html("\n");
|
||||
}
|
||||
|
||||
static void emit_blame_entry_linenumber(struct blame_entry *ent)
|
||||
{
|
||||
const char *numberfmt = "<a id='n%1$d' href='#n%1$d'>%1$d</a>\n";
|
||||
|
||||
unsigned long lineno = ent->lno;
|
||||
while (lineno < ent->lno + ent->num_lines)
|
||||
htmlf(numberfmt, ++lineno);
|
||||
}
|
||||
|
||||
static void emit_blame_entry_line_background(struct blame_scoreboard *sb,
|
||||
struct blame_entry *ent)
|
||||
{
|
||||
unsigned long line;
|
||||
size_t len, maxlen = 2;
|
||||
const char* pos, *endpos;
|
||||
|
||||
for (line = ent->lno; line < ent->lno + ent->num_lines; line++) {
|
||||
html("\n");
|
||||
pos = blame_nth_line(sb, line);
|
||||
endpos = blame_nth_line(sb, line + 1);
|
||||
len = 0;
|
||||
while (pos < endpos) {
|
||||
len++;
|
||||
if (*pos++ == '\t')
|
||||
len = (len + 7) & ~7;
|
||||
}
|
||||
if (len > maxlen)
|
||||
maxlen = len;
|
||||
}
|
||||
|
||||
for (len = 0; len < maxlen - 1; len++)
|
||||
html(" ");
|
||||
}
|
||||
|
||||
struct walk_tree_context {
|
||||
char *curr_rev;
|
||||
int match_baselen;
|
||||
int state;
|
||||
};
|
||||
|
||||
static void print_object(const struct object_id *oid, const char *path,
|
||||
const char *basename, const char *rev)
|
||||
{
|
||||
enum object_type type;
|
||||
char *buf;
|
||||
unsigned long size;
|
||||
struct argv_array rev_argv = ARGV_ARRAY_INIT;
|
||||
struct rev_info revs;
|
||||
struct blame_scoreboard sb;
|
||||
struct blame_origin *o;
|
||||
struct blame_entry *ent = NULL;
|
||||
|
||||
type = oid_object_info(the_repository, oid, &size);
|
||||
if (type == OBJ_BAD) {
|
||||
cgit_print_error_page(404, "Not found", "Bad object name: %s",
|
||||
oid_to_hex(oid));
|
||||
return;
|
||||
}
|
||||
|
||||
buf = read_object_file(oid, &type, &size);
|
||||
if (!buf) {
|
||||
cgit_print_error_page(500, "Internal server error",
|
||||
"Error reading object %s", oid_to_hex(oid));
|
||||
return;
|
||||
}
|
||||
|
||||
argv_array_push(&rev_argv, "blame");
|
||||
argv_array_push(&rev_argv, rev);
|
||||
init_revisions(&revs, NULL);
|
||||
revs.diffopt.flags.allow_textconv = 1;
|
||||
setup_revisions(rev_argv.argc, rev_argv.argv, &revs, NULL);
|
||||
init_scoreboard(&sb);
|
||||
sb.revs = &revs;
|
||||
sb.repo = the_repository;
|
||||
setup_scoreboard(&sb, path, &o);
|
||||
o->suspects = blame_entry_prepend(NULL, 0, sb.num_lines, o);
|
||||
prio_queue_put(&sb.commits, o->commit);
|
||||
blame_origin_decref(o);
|
||||
sb.ent = NULL;
|
||||
sb.path = path;
|
||||
assign_blame(&sb, 0);
|
||||
blame_sort_final(&sb);
|
||||
blame_coalesce(&sb);
|
||||
|
||||
cgit_set_title_from_path(path);
|
||||
|
||||
cgit_print_layout_start();
|
||||
htmlf("blob: %s (", oid_to_hex(oid));
|
||||
cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path);
|
||||
html(") (");
|
||||
cgit_tree_link("tree", NULL, NULL, ctx.qry.head, rev, path);
|
||||
html(")\n");
|
||||
|
||||
if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) {
|
||||
htmlf("<div class='error'>blob size (%ldKB)"
|
||||
" exceeds display size limit (%dKB).</div>",
|
||||
size / 1024, ctx.cfg.max_blob_size);
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
html("<table class='blame blob'>\n<tr>\n");
|
||||
|
||||
/* Commit hashes */
|
||||
html("<td class='hashes'>");
|
||||
for (ent = sb.ent; ent; ent = ent->next) {
|
||||
html("<div class='alt'><pre>");
|
||||
emit_blame_entry_hash(ent);
|
||||
html("</pre></div>");
|
||||
}
|
||||
html("</td>\n");
|
||||
|
||||
/* Line numbers */
|
||||
if (ctx.cfg.enable_tree_linenumbers) {
|
||||
html("<td class='linenumbers'>");
|
||||
for (ent = sb.ent; ent; ent = ent->next) {
|
||||
html("<div class='alt'><pre>");
|
||||
emit_blame_entry_linenumber(ent);
|
||||
html("</pre></div>");
|
||||
}
|
||||
html("</td>\n");
|
||||
}
|
||||
|
||||
html("<td class='lines'><div>");
|
||||
|
||||
/* Colored bars behind lines */
|
||||
html("<div>");
|
||||
for (ent = sb.ent; ent; ) {
|
||||
struct blame_entry *e = ent->next;
|
||||
html("<div class='alt'><pre>");
|
||||
emit_blame_entry_line_background(&sb, ent);
|
||||
html("</pre></div>");
|
||||
free(ent);
|
||||
ent = e;
|
||||
}
|
||||
html("</div>");
|
||||
|
||||
free((void *)sb.final_buf);
|
||||
|
||||
/* Lines */
|
||||
html("<pre><code>");
|
||||
if (ctx.repo->source_filter) {
|
||||
char *filter_arg = xstrdup(basename);
|
||||
cgit_open_filter(ctx.repo->source_filter, filter_arg);
|
||||
html_raw(buf, size);
|
||||
cgit_close_filter(ctx.repo->source_filter);
|
||||
free(filter_arg);
|
||||
} else {
|
||||
html_txt(buf);
|
||||
}
|
||||
html("</code></pre>");
|
||||
|
||||
html("</div></td>\n");
|
||||
|
||||
html("</tr>\n</table>\n");
|
||||
|
||||
cgit_print_layout_end();
|
||||
|
||||
cleanup:
|
||||
free(buf);
|
||||
}
|
||||
|
||||
static int walk_tree(const struct object_id *oid, struct strbuf *base,
|
||||
const char *pathname, unsigned mode, int stage,
|
||||
void *cbdata)
|
||||
{
|
||||
struct walk_tree_context *walk_tree_ctx = cbdata;
|
||||
|
||||
if (base->len == walk_tree_ctx->match_baselen) {
|
||||
if (S_ISREG(mode)) {
|
||||
struct strbuf buffer = STRBUF_INIT;
|
||||
strbuf_addbuf(&buffer, base);
|
||||
strbuf_addstr(&buffer, pathname);
|
||||
print_object(oid, buffer.buf, pathname,
|
||||
walk_tree_ctx->curr_rev);
|
||||
strbuf_release(&buffer);
|
||||
walk_tree_ctx->state = 1;
|
||||
} else if (S_ISDIR(mode)) {
|
||||
walk_tree_ctx->state = 2;
|
||||
}
|
||||
} else if (base->len < INT_MAX
|
||||
&& (int)base->len > walk_tree_ctx->match_baselen) {
|
||||
walk_tree_ctx->state = 2;
|
||||
} else if (S_ISDIR(mode)) {
|
||||
return READ_TREE_RECURSIVE;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int basedir_len(const char *path)
|
||||
{
|
||||
char *p = strrchr(path, '/');
|
||||
if (p)
|
||||
return p - path + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cgit_print_blame(void)
|
||||
{
|
||||
const char *rev = ctx.qry.sha1;
|
||||
struct object_id oid;
|
||||
struct commit *commit;
|
||||
struct pathspec_item path_items = {
|
||||
.match = ctx.qry.path,
|
||||
.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
|
||||
};
|
||||
struct pathspec paths = {
|
||||
.nr = 1,
|
||||
.items = &path_items
|
||||
};
|
||||
struct walk_tree_context walk_tree_ctx = {
|
||||
.state = 0
|
||||
};
|
||||
|
||||
if (!rev)
|
||||
rev = ctx.qry.head;
|
||||
|
||||
if (get_oid(rev, &oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Invalid revision name: %s", rev);
|
||||
return;
|
||||
}
|
||||
commit = lookup_commit_reference(the_repository, &oid);
|
||||
if (!commit || parse_commit(commit)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Invalid commit reference: %s", rev);
|
||||
return;
|
||||
}
|
||||
|
||||
walk_tree_ctx.curr_rev = xstrdup(rev);
|
||||
walk_tree_ctx.match_baselen = (path_items.match) ?
|
||||
basedir_len(path_items.match) : -1;
|
||||
|
||||
read_tree_recursive(the_repository, commit->maybe_tree, "", 0, 0,
|
||||
&paths, walk_tree, &walk_tree_ctx);
|
||||
if (!walk_tree_ctx.state)
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
else if (walk_tree_ctx.state == 2)
|
||||
cgit_print_error_page(404, "No blame for folders",
|
||||
"Blame is not available for folders.");
|
||||
|
||||
free(walk_tree_ctx.curr_rev);
|
||||
}
|
6
ui-blame.h
Normal file
6
ui-blame.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef UI_BLAME_H
|
||||
#define UI_BLAME_H
|
||||
|
||||
extern void cgit_print_blame(void);
|
||||
|
||||
#endif /* UI_BLAME_H */
|
181
ui-blob.c
Normal file
181
ui-blob.c
Normal file
|
@ -0,0 +1,181 @@
|
|||
/* ui-blob.c: show blob content
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-blob.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
struct walk_tree_context {
|
||||
const char *match_path;
|
||||
struct object_id *matched_oid;
|
||||
unsigned int found_path:1;
|
||||
unsigned int file_only:1;
|
||||
};
|
||||
|
||||
static int walk_tree(const struct object_id *oid, struct strbuf *base,
|
||||
const char *pathname, unsigned mode, int stage, void *cbdata)
|
||||
{
|
||||
struct walk_tree_context *walk_tree_ctx = cbdata;
|
||||
|
||||
if (walk_tree_ctx->file_only && !S_ISREG(mode))
|
||||
return READ_TREE_RECURSIVE;
|
||||
if (strncmp(base->buf, walk_tree_ctx->match_path, base->len)
|
||||
|| strcmp(walk_tree_ctx->match_path + base->len, pathname))
|
||||
return READ_TREE_RECURSIVE;
|
||||
oidcpy(walk_tree_ctx->matched_oid, oid);
|
||||
walk_tree_ctx->found_path = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cgit_ref_path_exists(const char *path, const char *ref, int file_only)
|
||||
{
|
||||
struct object_id oid;
|
||||
unsigned long size;
|
||||
struct pathspec_item path_items = {
|
||||
.match = xstrdup(path),
|
||||
.len = strlen(path)
|
||||
};
|
||||
struct pathspec paths = {
|
||||
.nr = 1,
|
||||
.items = &path_items
|
||||
};
|
||||
struct walk_tree_context walk_tree_ctx = {
|
||||
.match_path = path,
|
||||
.matched_oid = &oid,
|
||||
.found_path = 0,
|
||||
.file_only = file_only
|
||||
};
|
||||
|
||||
if (get_oid(ref, &oid))
|
||||
goto done;
|
||||
if (oid_object_info(the_repository, &oid, &size) != OBJ_COMMIT)
|
||||
goto done;
|
||||
read_tree_recursive(the_repository, lookup_commit_reference(the_repository, &oid)->maybe_tree,
|
||||
"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
|
||||
|
||||
done:
|
||||
free(path_items.match);
|
||||
return walk_tree_ctx.found_path;
|
||||
}
|
||||
|
||||
int cgit_print_file(char *path, const char *head, int file_only)
|
||||
{
|
||||
struct object_id oid;
|
||||
enum object_type type;
|
||||
char *buf;
|
||||
unsigned long size;
|
||||
struct commit *commit;
|
||||
struct pathspec_item path_items = {
|
||||
.match = path,
|
||||
.len = strlen(path)
|
||||
};
|
||||
struct pathspec paths = {
|
||||
.nr = 1,
|
||||
.items = &path_items
|
||||
};
|
||||
struct walk_tree_context walk_tree_ctx = {
|
||||
.match_path = path,
|
||||
.matched_oid = &oid,
|
||||
.found_path = 0,
|
||||
.file_only = file_only
|
||||
};
|
||||
|
||||
if (get_oid(head, &oid))
|
||||
return -1;
|
||||
type = oid_object_info(the_repository, &oid, &size);
|
||||
if (type == OBJ_COMMIT) {
|
||||
commit = lookup_commit_reference(the_repository, &oid);
|
||||
read_tree_recursive(the_repository, commit->maybe_tree,
|
||||
"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
|
||||
if (!walk_tree_ctx.found_path)
|
||||
return -1;
|
||||
type = oid_object_info(the_repository, &oid, &size);
|
||||
}
|
||||
if (type == OBJ_BAD)
|
||||
return -1;
|
||||
buf = read_object_file(&oid, &type, &size);
|
||||
if (!buf)
|
||||
return -1;
|
||||
buf[size] = '\0';
|
||||
html_raw(buf, size);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cgit_print_blob(const char *hex, char *path, const char *head, int file_only)
|
||||
{
|
||||
struct object_id oid;
|
||||
enum object_type type;
|
||||
char *buf;
|
||||
unsigned long size;
|
||||
struct commit *commit;
|
||||
struct pathspec_item path_items = {
|
||||
.match = path,
|
||||
.len = path ? strlen(path) : 0
|
||||
};
|
||||
struct pathspec paths = {
|
||||
.nr = 1,
|
||||
.items = &path_items
|
||||
};
|
||||
struct walk_tree_context walk_tree_ctx = {
|
||||
.match_path = path,
|
||||
.matched_oid = &oid,
|
||||
.found_path = 0,
|
||||
.file_only = file_only
|
||||
};
|
||||
|
||||
if (hex) {
|
||||
if (get_oid_hex(hex, &oid)) {
|
||||
cgit_print_error_page(400, "Bad request",
|
||||
"Bad hex value: %s", hex);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (get_oid(head, &oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad ref: %s", head);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
type = oid_object_info(the_repository, &oid, &size);
|
||||
|
||||
if ((!hex) && type == OBJ_COMMIT && path) {
|
||||
commit = lookup_commit_reference(the_repository, &oid);
|
||||
read_tree_recursive(the_repository, commit->maybe_tree,
|
||||
"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
|
||||
type = oid_object_info(the_repository, &oid, &size);
|
||||
}
|
||||
|
||||
if (type == OBJ_BAD) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad object name: %s", hex);
|
||||
return;
|
||||
}
|
||||
|
||||
buf = read_object_file(&oid, &type, &size);
|
||||
if (!buf) {
|
||||
cgit_print_error_page(500, "Internal server error",
|
||||
"Error reading object %s", hex);
|
||||
return;
|
||||
}
|
||||
|
||||
buf[size] = '\0';
|
||||
if (buffer_is_binary(buf, size))
|
||||
ctx.page.mimetype = "application/octet-stream";
|
||||
else
|
||||
ctx.page.mimetype = "text/plain";
|
||||
ctx.page.filename = path;
|
||||
|
||||
html("X-Content-Type-Options: nosniff\n");
|
||||
html("Content-Security-Policy: default-src 'none'\n");
|
||||
cgit_print_http_headers();
|
||||
html_raw(buf, size);
|
||||
free(buf);
|
||||
}
|
8
ui-blob.h
Normal file
8
ui-blob.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifndef UI_BLOB_H
|
||||
#define UI_BLOB_H
|
||||
|
||||
extern int cgit_ref_path_exists(const char *path, const char *ref, int file_only);
|
||||
extern int cgit_print_file(char *path, const char *head, int file_only);
|
||||
extern void cgit_print_blob(const char *hex, char *path, const char *head, int file_only);
|
||||
|
||||
#endif /* UI_BLOB_H */
|
126
ui-clone.c
Normal file
126
ui-clone.c
Normal file
|
@ -0,0 +1,126 @@
|
|||
/* ui-clone.c: functions for http cloning, based on
|
||||
* git's http-backend.c by Shawn O. Pearce
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-clone.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
#include "packfile.h"
|
||||
#include "object-store.h"
|
||||
|
||||
static int print_ref_info(const char *refname, const struct object_id *oid,
|
||||
int flags, void *cb_data)
|
||||
{
|
||||
struct object *obj;
|
||||
|
||||
if (!(obj = parse_object(the_repository, oid)))
|
||||
return 0;
|
||||
|
||||
htmlf("%s\t%s\n", oid_to_hex(oid), refname);
|
||||
if (obj->type == OBJ_TAG) {
|
||||
if (!(obj = deref_tag(the_repository, obj, refname, 0)))
|
||||
return 0;
|
||||
htmlf("%s\t%s^{}\n", oid_to_hex(&obj->oid), refname);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_pack_info(void)
|
||||
{
|
||||
struct packed_git *pack;
|
||||
char *offset;
|
||||
|
||||
ctx.page.mimetype = "text/plain";
|
||||
ctx.page.filename = "objects/info/packs";
|
||||
cgit_print_http_headers();
|
||||
reprepare_packed_git(the_repository);
|
||||
for (pack = get_packed_git(the_repository); pack; pack = pack->next) {
|
||||
if (pack->pack_local) {
|
||||
offset = strrchr(pack->pack_name, '/');
|
||||
if (offset && offset[1] != '\0')
|
||||
++offset;
|
||||
else
|
||||
offset = pack->pack_name;
|
||||
htmlf("P %s\n", offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void send_file(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
|
||||
if (stat(path, &st)) {
|
||||
switch (errno) {
|
||||
case ENOENT:
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
break;
|
||||
case EACCES:
|
||||
cgit_print_error_page(403, "Forbidden", "Forbidden");
|
||||
break;
|
||||
default:
|
||||
cgit_print_error_page(400, "Bad request", "Bad request");
|
||||
}
|
||||
return;
|
||||
}
|
||||
ctx.page.mimetype = "application/octet-stream";
|
||||
ctx.page.filename = path;
|
||||
skip_prefix(path, ctx.repo->path, &ctx.page.filename);
|
||||
skip_prefix(ctx.page.filename, "/", &ctx.page.filename);
|
||||
cgit_print_http_headers();
|
||||
html_include(path);
|
||||
}
|
||||
|
||||
void cgit_clone_info(void)
|
||||
{
|
||||
if (!ctx.qry.path || strcmp(ctx.qry.path, "refs")) {
|
||||
cgit_print_error_page(400, "Bad request", "Bad request");
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.page.mimetype = "text/plain";
|
||||
ctx.page.filename = "info/refs";
|
||||
cgit_print_http_headers();
|
||||
for_each_ref(print_ref_info, NULL);
|
||||
}
|
||||
|
||||
void cgit_clone_objects(void)
|
||||
{
|
||||
char *p;
|
||||
|
||||
if (!ctx.qry.path)
|
||||
goto err;
|
||||
|
||||
if (!strcmp(ctx.qry.path, "info/packs")) {
|
||||
print_pack_info();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Avoid directory traversal by forbidding "..", but also work around
|
||||
* other funny business by just specifying a fairly strict format. For
|
||||
* example, now we don't have to stress out about the Cygwin port.
|
||||
*/
|
||||
for (p = ctx.qry.path; *p; ++p) {
|
||||
if (*p == '.' && *(p + 1) == '.')
|
||||
goto err;
|
||||
if (!isalnum(*p) && *p != '/' && *p != '.' && *p != '-')
|
||||
goto err;
|
||||
}
|
||||
|
||||
send_file(git_path("objects/%s", ctx.qry.path));
|
||||
return;
|
||||
|
||||
err:
|
||||
cgit_print_error_page(400, "Bad request", "Bad request");
|
||||
}
|
||||
|
||||
void cgit_clone_head(void)
|
||||
{
|
||||
send_file(git_path("%s", "HEAD"));
|
||||
}
|
8
ui-clone.h
Normal file
8
ui-clone.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifndef UI_CLONE_H
|
||||
#define UI_CLONE_H
|
||||
|
||||
void cgit_clone_info(void);
|
||||
void cgit_clone_objects(void);
|
||||
void cgit_clone_head(void);
|
||||
|
||||
#endif /* UI_CLONE_H */
|
147
ui-commit.c
Normal file
147
ui-commit.c
Normal file
|
@ -0,0 +1,147 @@
|
|||
/* ui-commit.c: generate commit view
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-commit.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
#include "ui-diff.h"
|
||||
#include "ui-log.h"
|
||||
|
||||
void cgit_print_commit(char *hex, const char *prefix)
|
||||
{
|
||||
struct commit *commit, *parent;
|
||||
struct commitinfo *info, *parent_info;
|
||||
struct commit_list *p;
|
||||
struct strbuf notes = STRBUF_INIT;
|
||||
struct object_id oid;
|
||||
char *tmp, *tmp2;
|
||||
int parents = 0;
|
||||
|
||||
if (!hex)
|
||||
hex = ctx.qry.head;
|
||||
|
||||
if (get_oid(hex, &oid)) {
|
||||
cgit_print_error_page(400, "Bad request",
|
||||
"Bad object id: %s", hex);
|
||||
return;
|
||||
}
|
||||
commit = lookup_commit_reference(the_repository, &oid);
|
||||
if (!commit) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad commit reference: %s", hex);
|
||||
return;
|
||||
}
|
||||
info = cgit_parse_commit(commit);
|
||||
|
||||
format_display_notes(&oid, ¬es, PAGE_ENCODING, 0);
|
||||
|
||||
load_ref_decorations(NULL, DECORATE_FULL_REFS);
|
||||
|
||||
cgit_print_layout_start();
|
||||
cgit_print_diff_ctrls();
|
||||
html("<table summary='commit info' class='commit-info'>\n");
|
||||
html("<tr><th>author</th><td>");
|
||||
cgit_open_filter(ctx.repo->email_filter, info->author_email, "commit");
|
||||
html_txt(info->author);
|
||||
if (!ctx.cfg.noplainemail) {
|
||||
html(" ");
|
||||
html_txt(info->author_email);
|
||||
}
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
html("</td><td class='right'>");
|
||||
html_txt(show_date(info->author_date, info->author_tz,
|
||||
cgit_date_mode(DATE_ISO8601)));
|
||||
html("</td></tr>\n");
|
||||
html("<tr><th>committer</th><td>");
|
||||
cgit_open_filter(ctx.repo->email_filter, info->committer_email, "commit");
|
||||
html_txt(info->committer);
|
||||
if (!ctx.cfg.noplainemail) {
|
||||
html(" ");
|
||||
html_txt(info->committer_email);
|
||||
}
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
html("</td><td class='right'>");
|
||||
html_txt(show_date(info->committer_date, info->committer_tz,
|
||||
cgit_date_mode(DATE_ISO8601)));
|
||||
html("</td></tr>\n");
|
||||
html("<tr><th>commit</th><td colspan='2' class='sha1'>");
|
||||
tmp = oid_to_hex(&commit->object.oid);
|
||||
cgit_commit_link(tmp, NULL, NULL, ctx.qry.head, tmp, prefix);
|
||||
html(" (");
|
||||
cgit_patch_link("patch", NULL, NULL, NULL, tmp, prefix);
|
||||
html(")</td></tr>\n");
|
||||
html("<tr><th>tree</th><td colspan='2' class='sha1'>");
|
||||
tmp = xstrdup(hex);
|
||||
cgit_tree_link(oid_to_hex(&commit->maybe_tree->object.oid), NULL, NULL,
|
||||
ctx.qry.head, tmp, NULL);
|
||||
if (prefix) {
|
||||
html(" /");
|
||||
cgit_tree_link(prefix, NULL, NULL, ctx.qry.head, tmp, prefix);
|
||||
}
|
||||
free(tmp);
|
||||
html("</td></tr>\n");
|
||||
for (p = commit->parents; p; p = p->next) {
|
||||
parent = lookup_commit_reference(the_repository, &p->item->object.oid);
|
||||
if (!parent) {
|
||||
html("<tr><td colspan='3'>");
|
||||
cgit_print_error("Error reading parent commit");
|
||||
html("</td></tr>");
|
||||
continue;
|
||||
}
|
||||
html("<tr><th>parent</th>"
|
||||
"<td colspan='2' class='sha1'>");
|
||||
tmp = tmp2 = oid_to_hex(&p->item->object.oid);
|
||||
if (ctx.repo->enable_subject_links) {
|
||||
parent_info = cgit_parse_commit(parent);
|
||||
tmp2 = parent_info->subject;
|
||||
}
|
||||
cgit_commit_link(tmp2, NULL, NULL, ctx.qry.head, tmp, prefix);
|
||||
html(" (");
|
||||
cgit_diff_link("diff", NULL, NULL, ctx.qry.head, hex,
|
||||
oid_to_hex(&p->item->object.oid), prefix);
|
||||
html(")</td></tr>");
|
||||
parents++;
|
||||
}
|
||||
if (ctx.repo->snapshots) {
|
||||
html("<tr><th>download</th><td colspan='2' class='sha1'>");
|
||||
cgit_print_snapshot_links(ctx.repo, hex, "<br/>");
|
||||
html("</td></tr>");
|
||||
}
|
||||
html("</table>\n");
|
||||
html("<div class='commit-subject'>");
|
||||
cgit_open_filter(ctx.repo->commit_filter);
|
||||
html_txt(info->subject);
|
||||
cgit_close_filter(ctx.repo->commit_filter);
|
||||
show_commit_decorations(commit);
|
||||
html("</div>");
|
||||
html("<div class='commit-msg'>");
|
||||
cgit_open_filter(ctx.repo->commit_filter);
|
||||
html_txt(info->msg);
|
||||
cgit_close_filter(ctx.repo->commit_filter);
|
||||
html("</div>");
|
||||
if (notes.len != 0) {
|
||||
html("<div class='notes-header'>Notes</div>");
|
||||
html("<div class='notes'>");
|
||||
cgit_open_filter(ctx.repo->commit_filter);
|
||||
html_txt(notes.buf);
|
||||
cgit_close_filter(ctx.repo->commit_filter);
|
||||
html("</div>");
|
||||
html("<div class='notes-footer'></div>");
|
||||
}
|
||||
if (parents < 3) {
|
||||
if (parents)
|
||||
tmp = oid_to_hex(&commit->parents->item->object.oid);
|
||||
else
|
||||
tmp = NULL;
|
||||
cgit_print_diff(ctx.qry.sha1, tmp, prefix, 0, 0);
|
||||
}
|
||||
strbuf_release(¬es);
|
||||
cgit_free_commitinfo(info);
|
||||
cgit_print_layout_end();
|
||||
}
|
6
ui-commit.h
Normal file
6
ui-commit.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef UI_COMMIT_H
|
||||
#define UI_COMMIT_H
|
||||
|
||||
extern void cgit_print_commit(char *hex, const char *prefix);
|
||||
|
||||
#endif /* UI_COMMIT_H */
|
501
ui-diff.c
Normal file
501
ui-diff.c
Normal file
|
@ -0,0 +1,501 @@
|
|||
/* ui-diff.c: show diff between two blobs
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-diff.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
#include "ui-ssdiff.h"
|
||||
|
||||
struct object_id old_rev_oid[1];
|
||||
struct object_id new_rev_oid[1];
|
||||
|
||||
static int files, slots;
|
||||
static int total_adds, total_rems, max_changes;
|
||||
static int lines_added, lines_removed;
|
||||
|
||||
static struct fileinfo {
|
||||
char status;
|
||||
struct object_id old_oid[1];
|
||||
struct object_id new_oid[1];
|
||||
unsigned short old_mode;
|
||||
unsigned short new_mode;
|
||||
char *old_path;
|
||||
char *new_path;
|
||||
unsigned int added;
|
||||
unsigned int removed;
|
||||
unsigned long old_size;
|
||||
unsigned long new_size;
|
||||
unsigned int binary:1;
|
||||
} *items;
|
||||
|
||||
static int use_ssdiff = 0;
|
||||
static struct diff_filepair *current_filepair;
|
||||
static const char *current_prefix;
|
||||
|
||||
struct diff_filespec *cgit_get_current_old_file(void)
|
||||
{
|
||||
return current_filepair->one;
|
||||
}
|
||||
|
||||
struct diff_filespec *cgit_get_current_new_file(void)
|
||||
{
|
||||
return current_filepair->two;
|
||||
}
|
||||
|
||||
static void print_fileinfo(struct fileinfo *info)
|
||||
{
|
||||
char *class;
|
||||
|
||||
switch (info->status) {
|
||||
case DIFF_STATUS_ADDED:
|
||||
class = "add";
|
||||
break;
|
||||
case DIFF_STATUS_COPIED:
|
||||
class = "cpy";
|
||||
break;
|
||||
case DIFF_STATUS_DELETED:
|
||||
class = "del";
|
||||
break;
|
||||
case DIFF_STATUS_MODIFIED:
|
||||
class = "upd";
|
||||
break;
|
||||
case DIFF_STATUS_RENAMED:
|
||||
class = "mov";
|
||||
break;
|
||||
case DIFF_STATUS_TYPE_CHANGED:
|
||||
class = "typ";
|
||||
break;
|
||||
case DIFF_STATUS_UNKNOWN:
|
||||
class = "unk";
|
||||
break;
|
||||
case DIFF_STATUS_UNMERGED:
|
||||
class = "stg";
|
||||
break;
|
||||
default:
|
||||
die("bug: unhandled diff status %c", info->status);
|
||||
}
|
||||
|
||||
html("<tr>");
|
||||
html("<td class='mode'>");
|
||||
if (is_null_oid(info->new_oid)) {
|
||||
cgit_print_filemode(info->old_mode);
|
||||
} else {
|
||||
cgit_print_filemode(info->new_mode);
|
||||
}
|
||||
|
||||
if (info->old_mode != info->new_mode &&
|
||||
!is_null_oid(info->old_oid) &&
|
||||
!is_null_oid(info->new_oid)) {
|
||||
html("<span class='modechange'>[");
|
||||
cgit_print_filemode(info->old_mode);
|
||||
html("]</span>");
|
||||
}
|
||||
htmlf("</td><td class='%s'>", class);
|
||||
cgit_diff_link(info->new_path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
|
||||
ctx.qry.sha2, info->new_path);
|
||||
if (info->status == DIFF_STATUS_COPIED || info->status == DIFF_STATUS_RENAMED) {
|
||||
htmlf(" (%s from ",
|
||||
info->status == DIFF_STATUS_COPIED ? "copied" : "renamed");
|
||||
html_txt(info->old_path);
|
||||
html(")");
|
||||
}
|
||||
html("</td><td class='right'>");
|
||||
if (info->binary) {
|
||||
htmlf("bin</td><td class='graph'>%ld -> %ld bytes",
|
||||
info->old_size, info->new_size);
|
||||
return;
|
||||
}
|
||||
htmlf("%d", info->added + info->removed);
|
||||
html("</td><td class='graph'>");
|
||||
htmlf("<table summary='file diffstat' width='%d%%'><tr>", (max_changes > 100 ? 100 : max_changes));
|
||||
htmlf("<td class='add' style='width: %.1f%%;'/>",
|
||||
info->added * 100.0 / max_changes);
|
||||
htmlf("<td class='rem' style='width: %.1f%%;'/>",
|
||||
info->removed * 100.0 / max_changes);
|
||||
htmlf("<td class='none' style='width: %.1f%%;'/>",
|
||||
(max_changes - info->removed - info->added) * 100.0 / max_changes);
|
||||
html("</tr></table></td></tr>\n");
|
||||
}
|
||||
|
||||
static void count_diff_lines(char *line, int len)
|
||||
{
|
||||
if (line && (len > 0)) {
|
||||
if (line[0] == '+')
|
||||
lines_added++;
|
||||
else if (line[0] == '-')
|
||||
lines_removed++;
|
||||
}
|
||||
}
|
||||
|
||||
static int show_filepair(struct diff_filepair *pair)
|
||||
{
|
||||
/* Always show if we have no limiting prefix. */
|
||||
if (!current_prefix)
|
||||
return 1;
|
||||
|
||||
/* Show if either path in the pair begins with the prefix. */
|
||||
if (starts_with(pair->one->path, current_prefix) ||
|
||||
starts_with(pair->two->path, current_prefix))
|
||||
return 1;
|
||||
|
||||
/* Otherwise we don't want to show this filepair. */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void inspect_filepair(struct diff_filepair *pair)
|
||||
{
|
||||
int binary = 0;
|
||||
unsigned long old_size = 0;
|
||||
unsigned long new_size = 0;
|
||||
|
||||
if (!show_filepair(pair))
|
||||
return;
|
||||
|
||||
files++;
|
||||
lines_added = 0;
|
||||
lines_removed = 0;
|
||||
cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size, &new_size,
|
||||
&binary, 0, ctx.qry.ignorews, count_diff_lines);
|
||||
if (files >= slots) {
|
||||
if (slots == 0)
|
||||
slots = 4;
|
||||
else
|
||||
slots = slots * 2;
|
||||
items = xrealloc(items, slots * sizeof(struct fileinfo));
|
||||
}
|
||||
items[files-1].status = pair->status;
|
||||
oidcpy(items[files-1].old_oid, &pair->one->oid);
|
||||
oidcpy(items[files-1].new_oid, &pair->two->oid);
|
||||
items[files-1].old_mode = pair->one->mode;
|
||||
items[files-1].new_mode = pair->two->mode;
|
||||
items[files-1].old_path = xstrdup(pair->one->path);
|
||||
items[files-1].new_path = xstrdup(pair->two->path);
|
||||
items[files-1].added = lines_added;
|
||||
items[files-1].removed = lines_removed;
|
||||
items[files-1].old_size = old_size;
|
||||
items[files-1].new_size = new_size;
|
||||
items[files-1].binary = binary;
|
||||
if (lines_added + lines_removed > max_changes)
|
||||
max_changes = lines_added + lines_removed;
|
||||
total_adds += lines_added;
|
||||
total_rems += lines_removed;
|
||||
}
|
||||
|
||||
static void cgit_print_diffstat(const struct object_id *old_oid,
|
||||
const struct object_id *new_oid,
|
||||
const char *prefix)
|
||||
{
|
||||
int i;
|
||||
|
||||
html("<div class='diffstat-header'>");
|
||||
cgit_diff_link("Diffstat", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
|
||||
ctx.qry.sha2, NULL);
|
||||
if (prefix) {
|
||||
html(" (limited to '");
|
||||
html_txt(prefix);
|
||||
html("')");
|
||||
}
|
||||
html("</div>");
|
||||
html("<table summary='diffstat' class='diffstat'>");
|
||||
max_changes = 0;
|
||||
cgit_diff_tree(old_oid, new_oid, inspect_filepair, prefix,
|
||||
ctx.qry.ignorews);
|
||||
for (i = 0; i<files; i++)
|
||||
print_fileinfo(&items[i]);
|
||||
html("</table>");
|
||||
html("<div class='diffstat-summary'>");
|
||||
htmlf("%d files changed, %d insertions, %d deletions",
|
||||
files, total_adds, total_rems);
|
||||
html("</div>");
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* print a single line returned from xdiff
|
||||
*/
|
||||
static void print_line(char *line, int len)
|
||||
{
|
||||
char *class = "ctx";
|
||||
char c = line[len-1];
|
||||
|
||||
if (line[0] == '+')
|
||||
class = "add";
|
||||
else if (line[0] == '-')
|
||||
class = "del";
|
||||
else if (line[0] == '@')
|
||||
class = "hunk";
|
||||
|
||||
htmlf("<div class='%s'>", class);
|
||||
line[len-1] = '\0';
|
||||
html_txt(line);
|
||||
html("</div>");
|
||||
line[len-1] = c;
|
||||
}
|
||||
|
||||
static void header(const struct object_id *oid1, char *path1, int mode1,
|
||||
const struct object_id *oid2, char *path2, int mode2)
|
||||
{
|
||||
char *abbrev1, *abbrev2;
|
||||
int subproject;
|
||||
|
||||
subproject = (S_ISGITLINK(mode1) || S_ISGITLINK(mode2));
|
||||
html("<div class='head'>");
|
||||
html("diff --git a/");
|
||||
html_txt(path1);
|
||||
html(" b/");
|
||||
html_txt(path2);
|
||||
|
||||
if (mode1 == 0)
|
||||
htmlf("<br/>new file mode %.6o", mode2);
|
||||
|
||||
if (mode2 == 0)
|
||||
htmlf("<br/>deleted file mode %.6o", mode1);
|
||||
|
||||
if (!subproject) {
|
||||
abbrev1 = xstrdup(find_unique_abbrev(oid1, DEFAULT_ABBREV));
|
||||
abbrev2 = xstrdup(find_unique_abbrev(oid2, DEFAULT_ABBREV));
|
||||
htmlf("<br/>index %s..%s", abbrev1, abbrev2);
|
||||
free(abbrev1);
|
||||
free(abbrev2);
|
||||
if (mode1 != 0 && mode2 != 0) {
|
||||
htmlf(" %.6o", mode1);
|
||||
if (mode2 != mode1)
|
||||
htmlf("..%.6o", mode2);
|
||||
}
|
||||
if (is_null_oid(oid1)) {
|
||||
path1 = "dev/null";
|
||||
html("<br/>--- /");
|
||||
} else
|
||||
html("<br/>--- a/");
|
||||
if (mode1 != 0)
|
||||
cgit_tree_link(path1, NULL, NULL, ctx.qry.head,
|
||||
oid_to_hex(old_rev_oid), path1);
|
||||
else
|
||||
html_txt(path1);
|
||||
if (is_null_oid(oid2)) {
|
||||
path2 = "dev/null";
|
||||
html("<br/>+++ /");
|
||||
} else
|
||||
html("<br/>+++ b/");
|
||||
if (mode2 != 0)
|
||||
cgit_tree_link(path2, NULL, NULL, ctx.qry.head,
|
||||
oid_to_hex(new_rev_oid), path2);
|
||||
else
|
||||
html_txt(path2);
|
||||
}
|
||||
html("</div>");
|
||||
}
|
||||
|
||||
static void filepair_cb(struct diff_filepair *pair)
|
||||
{
|
||||
unsigned long old_size = 0;
|
||||
unsigned long new_size = 0;
|
||||
int binary = 0;
|
||||
linediff_fn print_line_fn = print_line;
|
||||
|
||||
if (!show_filepair(pair))
|
||||
return;
|
||||
|
||||
current_filepair = pair;
|
||||
if (use_ssdiff) {
|
||||
cgit_ssdiff_header_begin();
|
||||
print_line_fn = cgit_ssdiff_line_cb;
|
||||
}
|
||||
header(&pair->one->oid, pair->one->path, pair->one->mode,
|
||||
&pair->two->oid, pair->two->path, pair->two->mode);
|
||||
if (use_ssdiff)
|
||||
cgit_ssdiff_header_end();
|
||||
if (S_ISGITLINK(pair->one->mode) || S_ISGITLINK(pair->two->mode)) {
|
||||
if (S_ISGITLINK(pair->one->mode))
|
||||
print_line_fn(fmt("-Subproject %s", oid_to_hex(&pair->one->oid)), 52);
|
||||
if (S_ISGITLINK(pair->two->mode))
|
||||
print_line_fn(fmt("+Subproject %s", oid_to_hex(&pair->two->oid)), 52);
|
||||
if (use_ssdiff)
|
||||
cgit_ssdiff_footer();
|
||||
return;
|
||||
}
|
||||
if (cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size,
|
||||
&new_size, &binary, ctx.qry.context,
|
||||
ctx.qry.ignorews, print_line_fn))
|
||||
cgit_print_error("Error running diff");
|
||||
if (binary) {
|
||||
if (use_ssdiff)
|
||||
html("<tr><td colspan='4'>Binary files differ</td></tr>");
|
||||
else
|
||||
html("Binary files differ");
|
||||
}
|
||||
if (use_ssdiff)
|
||||
cgit_ssdiff_footer();
|
||||
}
|
||||
|
||||
void cgit_print_diff_ctrls(void)
|
||||
{
|
||||
int i, curr;
|
||||
|
||||
html("<div class='cgit-panel'>");
|
||||
html("<b>diff options</b>");
|
||||
html("<form method='get'>");
|
||||
cgit_add_hidden_formfields(1, 0, ctx.qry.page);
|
||||
html("<table>");
|
||||
html("<tr><td colspan='2'/></tr>");
|
||||
html("<tr>");
|
||||
html("<td class='label'>context:</td>");
|
||||
html("<td class='ctrl'>");
|
||||
html("<select name='context' onchange='this.form.submit();'>");
|
||||
curr = ctx.qry.context;
|
||||
if (!curr)
|
||||
curr = 3;
|
||||
for (i = 1; i <= 10; i++)
|
||||
html_intoption(i, fmt("%d", i), curr);
|
||||
for (i = 15; i <= 40; i += 5)
|
||||
html_intoption(i, fmt("%d", i), curr);
|
||||
html("</select>");
|
||||
html("</td>");
|
||||
html("</tr><tr>");
|
||||
html("<td class='label'>space:</td>");
|
||||
html("<td class='ctrl'>");
|
||||
html("<select name='ignorews' onchange='this.form.submit();'>");
|
||||
html_intoption(0, "include", ctx.qry.ignorews);
|
||||
html_intoption(1, "ignore", ctx.qry.ignorews);
|
||||
html("</select>");
|
||||
html("</td>");
|
||||
html("</tr><tr>");
|
||||
html("<td class='label'>mode:</td>");
|
||||
html("<td class='ctrl'>");
|
||||
html("<select name='dt' onchange='this.form.submit();'>");
|
||||
curr = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
|
||||
html_intoption(0, "unified", curr);
|
||||
html_intoption(1, "ssdiff", curr);
|
||||
html_intoption(2, "stat only", curr);
|
||||
html("</select></td></tr>");
|
||||
html("<tr><td/><td class='ctrl'>");
|
||||
html("<noscript><input type='submit' value='reload'/></noscript>");
|
||||
html("</td></tr></table>");
|
||||
html("</form>");
|
||||
html("</div>");
|
||||
}
|
||||
|
||||
void cgit_print_diff(const char *new_rev, const char *old_rev,
|
||||
const char *prefix, int show_ctrls, int raw)
|
||||
{
|
||||
struct commit *commit, *commit2;
|
||||
const struct object_id *old_tree_oid, *new_tree_oid;
|
||||
diff_type difftype;
|
||||
|
||||
/*
|
||||
* If "follow" is set then the diff machinery needs to examine the
|
||||
* entire commit to detect renames so we must limit the paths in our
|
||||
* own callbacks and not pass the prefix to the diff machinery.
|
||||
*/
|
||||
if (ctx.qry.follow && ctx.cfg.enable_follow_links) {
|
||||
current_prefix = prefix;
|
||||
prefix = "";
|
||||
} else {
|
||||
current_prefix = NULL;
|
||||
}
|
||||
|
||||
if (!new_rev)
|
||||
new_rev = ctx.qry.head;
|
||||
if (get_oid(new_rev, new_rev_oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad object name: %s", new_rev);
|
||||
return;
|
||||
}
|
||||
commit = lookup_commit_reference(the_repository, new_rev_oid);
|
||||
if (!commit || parse_commit(commit)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad commit: %s", oid_to_hex(new_rev_oid));
|
||||
return;
|
||||
}
|
||||
new_tree_oid = &commit->maybe_tree->object.oid;
|
||||
|
||||
if (old_rev) {
|
||||
if (get_oid(old_rev, old_rev_oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad object name: %s", old_rev);
|
||||
return;
|
||||
}
|
||||
} else if (commit->parents && commit->parents->item) {
|
||||
oidcpy(old_rev_oid, &commit->parents->item->object.oid);
|
||||
} else {
|
||||
oidclr(old_rev_oid);
|
||||
}
|
||||
|
||||
if (!is_null_oid(old_rev_oid)) {
|
||||
commit2 = lookup_commit_reference(the_repository, old_rev_oid);
|
||||
if (!commit2 || parse_commit(commit2)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad commit: %s", oid_to_hex(old_rev_oid));
|
||||
return;
|
||||
}
|
||||
old_tree_oid = &commit2->maybe_tree->object.oid;
|
||||
} else {
|
||||
old_tree_oid = NULL;
|
||||
}
|
||||
|
||||
if (raw) {
|
||||
struct diff_options diffopt;
|
||||
|
||||
diff_setup(&diffopt);
|
||||
diffopt.output_format = DIFF_FORMAT_PATCH;
|
||||
diffopt.flags.recursive = 1;
|
||||
diff_setup_done(&diffopt);
|
||||
|
||||
ctx.page.mimetype = "text/plain";
|
||||
cgit_print_http_headers();
|
||||
if (old_tree_oid) {
|
||||
diff_tree_oid(old_tree_oid, new_tree_oid, "",
|
||||
&diffopt);
|
||||
} else {
|
||||
diff_root_tree_oid(new_tree_oid, "", &diffopt);
|
||||
}
|
||||
diffcore_std(&diffopt);
|
||||
diff_flush(&diffopt);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
difftype = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
|
||||
use_ssdiff = difftype == DIFF_SSDIFF;
|
||||
|
||||
if (show_ctrls) {
|
||||
cgit_print_layout_start();
|
||||
cgit_print_diff_ctrls();
|
||||
}
|
||||
|
||||
/*
|
||||
* Clicking on a link to a file in the diff stat should show a diff
|
||||
* of the file, showing the diff stat limited to a single file is
|
||||
* pretty useless. All links from this point on will be to
|
||||
* individual files, so we simply reset the difftype in the query
|
||||
* here to avoid propagating DIFF_STATONLY to the individual files.
|
||||
*/
|
||||
if (difftype == DIFF_STATONLY)
|
||||
ctx.qry.difftype = ctx.cfg.difftype;
|
||||
|
||||
cgit_print_diffstat(old_rev_oid, new_rev_oid, prefix);
|
||||
|
||||
if (difftype == DIFF_STATONLY)
|
||||
return;
|
||||
|
||||
if (use_ssdiff) {
|
||||
html("<table summary='ssdiff' class='ssdiff'>");
|
||||
} else {
|
||||
html("<table summary='diff' class='diff'>");
|
||||
html("<tr><td>");
|
||||
}
|
||||
cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix,
|
||||
ctx.qry.ignorews);
|
||||
if (!use_ssdiff)
|
||||
html("</td></tr>");
|
||||
html("</table>");
|
||||
|
||||
if (show_ctrls)
|
||||
cgit_print_layout_end();
|
||||
}
|
15
ui-diff.h
Normal file
15
ui-diff.h
Normal file
|
@ -0,0 +1,15 @@
|
|||
#ifndef UI_DIFF_H
|
||||
#define UI_DIFF_H
|
||||
|
||||
extern void cgit_print_diff_ctrls(void);
|
||||
|
||||
extern void cgit_print_diff(const char *new_hex, const char *old_hex,
|
||||
const char *prefix, int show_ctrls, int raw);
|
||||
|
||||
extern struct diff_filespec *cgit_get_current_old_file(void);
|
||||
extern struct diff_filespec *cgit_get_current_new_file(void);
|
||||
|
||||
extern struct object_id old_rev_oid[1];
|
||||
extern struct object_id new_rev_oid[1];
|
||||
|
||||
#endif /* UI_DIFF_H */
|
550
ui-log.c
Normal file
550
ui-log.c
Normal file
|
@ -0,0 +1,550 @@
|
|||
/* ui-log.c: functions for log output
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-log.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
#include "argv-array.h"
|
||||
|
||||
static int files, add_lines, rem_lines, lines_counted;
|
||||
|
||||
/*
|
||||
* The list of available column colors in the commit graph.
|
||||
*/
|
||||
static const char *column_colors_html[] = {
|
||||
"<span class='column1'>",
|
||||
"<span class='column2'>",
|
||||
"<span class='column3'>",
|
||||
"<span class='column4'>",
|
||||
"<span class='column5'>",
|
||||
"<span class='column6'>",
|
||||
"</span>",
|
||||
};
|
||||
|
||||
#define COLUMN_COLORS_HTML_MAX (ARRAY_SIZE(column_colors_html) - 1)
|
||||
|
||||
static void count_lines(char *line, int size)
|
||||
{
|
||||
if (size <= 0)
|
||||
return;
|
||||
|
||||
if (line[0] == '+')
|
||||
add_lines++;
|
||||
|
||||
else if (line[0] == '-')
|
||||
rem_lines++;
|
||||
}
|
||||
|
||||
static void inspect_files(struct diff_filepair *pair)
|
||||
{
|
||||
unsigned long old_size = 0;
|
||||
unsigned long new_size = 0;
|
||||
int binary = 0;
|
||||
|
||||
files++;
|
||||
if (ctx.repo->enable_log_linecount)
|
||||
cgit_diff_files(&pair->one->oid, &pair->two->oid, &old_size,
|
||||
&new_size, &binary, 0, ctx.qry.ignorews,
|
||||
count_lines);
|
||||
}
|
||||
|
||||
void show_commit_decorations(struct commit *commit)
|
||||
{
|
||||
const struct name_decoration *deco;
|
||||
static char buf[1024];
|
||||
|
||||
buf[sizeof(buf) - 1] = 0;
|
||||
deco = get_name_decoration(&commit->object);
|
||||
if (!deco)
|
||||
return;
|
||||
html("<span class='decoration'>");
|
||||
while (deco) {
|
||||
struct object_id peeled;
|
||||
int is_annotated = 0;
|
||||
strlcpy(buf, prettify_refname(deco->name), sizeof(buf));
|
||||
switch(deco->type) {
|
||||
case DECORATION_NONE:
|
||||
/* If the git-core doesn't recognize it,
|
||||
* don't display anything. */
|
||||
break;
|
||||
case DECORATION_REF_LOCAL:
|
||||
cgit_log_link(buf, NULL, "branch-deco", buf, NULL,
|
||||
ctx.qry.vpath, 0, NULL, NULL,
|
||||
ctx.qry.showmsg, 0);
|
||||
break;
|
||||
case DECORATION_REF_TAG:
|
||||
if (!peel_ref(deco->name, &peeled))
|
||||
is_annotated = !oidcmp(&commit->object.oid, &peeled);
|
||||
cgit_tag_link(buf, NULL, is_annotated ? "tag-annotated-deco" : "tag-deco", buf);
|
||||
break;
|
||||
case DECORATION_REF_REMOTE:
|
||||
if (!ctx.repo->enable_remote_branches)
|
||||
break;
|
||||
cgit_log_link(buf, NULL, "remote-deco", NULL,
|
||||
oid_to_hex(&commit->object.oid),
|
||||
ctx.qry.vpath, 0, NULL, NULL,
|
||||
ctx.qry.showmsg, 0);
|
||||
break;
|
||||
default:
|
||||
cgit_commit_link(buf, NULL, "deco", ctx.qry.head,
|
||||
oid_to_hex(&commit->object.oid),
|
||||
ctx.qry.vpath);
|
||||
break;
|
||||
}
|
||||
deco = deco->next;
|
||||
}
|
||||
html("</span>");
|
||||
}
|
||||
|
||||
static void handle_rename(struct diff_filepair *pair)
|
||||
{
|
||||
/*
|
||||
* After we have seen a rename, we generate links to the previous
|
||||
* name of the file so that commit & diff views get fed the path
|
||||
* that is correct for the commit they are showing, avoiding the
|
||||
* need to walk the entire history leading back to every commit we
|
||||
* show in order detect renames.
|
||||
*/
|
||||
if (0 != strcmp(ctx.qry.vpath, pair->two->path)) {
|
||||
free(ctx.qry.vpath);
|
||||
ctx.qry.vpath = xstrdup(pair->two->path);
|
||||
}
|
||||
inspect_files(pair);
|
||||
}
|
||||
|
||||
static int show_commit(struct commit *commit, struct rev_info *revs)
|
||||
{
|
||||
struct commit_list *parents = commit->parents;
|
||||
struct commit *parent;
|
||||
int found = 0, saved_fmt;
|
||||
struct diff_flags saved_flags = revs->diffopt.flags;
|
||||
|
||||
/* Always show if we're not in "follow" mode with a single file. */
|
||||
if (!ctx.qry.follow)
|
||||
return 1;
|
||||
|
||||
/*
|
||||
* In "follow" mode, we don't show merges. This is consistent with
|
||||
* "git log --follow -- <file>".
|
||||
*/
|
||||
if (parents && parents->next)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* If this is the root commit, do what rev_info tells us.
|
||||
*/
|
||||
if (!parents)
|
||||
return revs->show_root_diff;
|
||||
|
||||
/* When we get here we have precisely one parent. */
|
||||
parent = parents->item;
|
||||
/* If we can't parse the commit, let print_commit() report an error. */
|
||||
if (parse_commit(parent))
|
||||
return 1;
|
||||
|
||||
files = 0;
|
||||
add_lines = 0;
|
||||
rem_lines = 0;
|
||||
|
||||
revs->diffopt.flags.recursive = 1;
|
||||
diff_tree_oid(&parent->maybe_tree->object.oid,
|
||||
&commit->maybe_tree->object.oid,
|
||||
"", &revs->diffopt);
|
||||
diffcore_std(&revs->diffopt);
|
||||
|
||||
found = !diff_queue_is_empty();
|
||||
saved_fmt = revs->diffopt.output_format;
|
||||
revs->diffopt.output_format = DIFF_FORMAT_CALLBACK;
|
||||
revs->diffopt.format_callback = cgit_diff_tree_cb;
|
||||
revs->diffopt.format_callback_data = handle_rename;
|
||||
diff_flush(&revs->diffopt);
|
||||
revs->diffopt.output_format = saved_fmt;
|
||||
revs->diffopt.flags = saved_flags;
|
||||
|
||||
lines_counted = 1;
|
||||
return found;
|
||||
}
|
||||
|
||||
static void print_commit(struct commit *commit, struct rev_info *revs)
|
||||
{
|
||||
struct commitinfo *info;
|
||||
int columns = revs->graph ? 4 : 3;
|
||||
struct strbuf graphbuf = STRBUF_INIT;
|
||||
struct strbuf msgbuf = STRBUF_INIT;
|
||||
|
||||
if (ctx.repo->enable_log_filecount)
|
||||
columns++;
|
||||
if (ctx.repo->enable_log_linecount)
|
||||
columns++;
|
||||
|
||||
if (revs->graph) {
|
||||
/* Advance graph until current commit */
|
||||
while (!graph_next_line(revs->graph, &graphbuf)) {
|
||||
/* Print graph segment in otherwise empty table row */
|
||||
html("<tr class='nohover'><td class='commitgraph'>");
|
||||
html(graphbuf.buf);
|
||||
htmlf("</td><td colspan='%d' /></tr>\n", columns);
|
||||
strbuf_setlen(&graphbuf, 0);
|
||||
}
|
||||
/* Current commit's graph segment is now ready in graphbuf */
|
||||
}
|
||||
|
||||
info = cgit_parse_commit(commit);
|
||||
htmlf("<tr%s>", ctx.qry.showmsg ? " class='logheader'" : "");
|
||||
|
||||
if (revs->graph) {
|
||||
/* Print graph segment for current commit */
|
||||
html("<td class='commitgraph'>");
|
||||
html(graphbuf.buf);
|
||||
html("</td>");
|
||||
strbuf_setlen(&graphbuf, 0);
|
||||
}
|
||||
else {
|
||||
html("<td>");
|
||||
cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2);
|
||||
html("</td>");
|
||||
}
|
||||
|
||||
htmlf("<td%s>", ctx.qry.showmsg ? " class='logsubject'" : "");
|
||||
if (ctx.qry.showmsg) {
|
||||
/* line-wrap long commit subjects instead of truncating them */
|
||||
size_t subject_len = strlen(info->subject);
|
||||
|
||||
if (subject_len > ctx.cfg.max_msg_len &&
|
||||
ctx.cfg.max_msg_len >= 15) {
|
||||
/* symbol for signaling line-wrap (in PAGE_ENCODING) */
|
||||
const char wrap_symbol[] = { ' ', 0xE2, 0x86, 0xB5, 0 };
|
||||
int i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
|
||||
|
||||
/* Rewind i to preceding space character */
|
||||
while (i > 0 && !isspace(info->subject[i]))
|
||||
--i;
|
||||
if (!i) /* Oops, zero spaces. Reset i */
|
||||
i = ctx.cfg.max_msg_len - strlen(wrap_symbol);
|
||||
|
||||
/* add remainder starting at i to msgbuf */
|
||||
strbuf_add(&msgbuf, info->subject + i, subject_len - i);
|
||||
strbuf_trim(&msgbuf);
|
||||
strbuf_add(&msgbuf, "\n\n", 2);
|
||||
|
||||
/* Place wrap_symbol at position i in info->subject */
|
||||
strlcpy(info->subject + i, wrap_symbol, subject_len - i + 1);
|
||||
}
|
||||
}
|
||||
cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head,
|
||||
oid_to_hex(&commit->object.oid), ctx.qry.vpath);
|
||||
show_commit_decorations(commit);
|
||||
html("</td><td>");
|
||||
cgit_open_filter(ctx.repo->email_filter, info->author_email, "log");
|
||||
html_txt(info->author);
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
|
||||
if (revs->graph) {
|
||||
html("</td><td>");
|
||||
cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2);
|
||||
}
|
||||
|
||||
if (!lines_counted && (ctx.repo->enable_log_filecount ||
|
||||
ctx.repo->enable_log_linecount)) {
|
||||
files = 0;
|
||||
add_lines = 0;
|
||||
rem_lines = 0;
|
||||
cgit_diff_commit(commit, inspect_files, ctx.qry.vpath);
|
||||
}
|
||||
|
||||
if (ctx.repo->enable_log_filecount)
|
||||
htmlf("</td><td>%d", files);
|
||||
if (ctx.repo->enable_log_linecount)
|
||||
htmlf("</td><td><span class='deletions'>-%d</span>/"
|
||||
"<span class='insertions'>+%d</span>", rem_lines, add_lines);
|
||||
|
||||
html("</td></tr>\n");
|
||||
|
||||
if ((revs->graph && !graph_is_commit_finished(revs->graph))
|
||||
|| ctx.qry.showmsg) { /* Print a second table row */
|
||||
html("<tr class='nohover-highlight'>");
|
||||
|
||||
if (ctx.qry.showmsg) {
|
||||
/* Concatenate commit message + notes in msgbuf */
|
||||
if (info->msg && *(info->msg)) {
|
||||
strbuf_addstr(&msgbuf, info->msg);
|
||||
strbuf_addch(&msgbuf, '\n');
|
||||
}
|
||||
format_display_notes(&commit->object.oid,
|
||||
&msgbuf, PAGE_ENCODING, 0);
|
||||
strbuf_addch(&msgbuf, '\n');
|
||||
strbuf_ltrim(&msgbuf);
|
||||
}
|
||||
|
||||
if (revs->graph) {
|
||||
int lines = 0;
|
||||
|
||||
/* Calculate graph padding */
|
||||
if (ctx.qry.showmsg) {
|
||||
/* Count #lines in commit message + notes */
|
||||
const char *p = msgbuf.buf;
|
||||
lines = 1;
|
||||
while ((p = strchr(p, '\n'))) {
|
||||
p++;
|
||||
lines++;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print graph padding */
|
||||
html("<td class='commitgraph'>");
|
||||
while (lines > 0 || !graph_is_commit_finished(revs->graph)) {
|
||||
if (graphbuf.len)
|
||||
html("\n");
|
||||
strbuf_setlen(&graphbuf, 0);
|
||||
graph_next_line(revs->graph, &graphbuf);
|
||||
html(graphbuf.buf);
|
||||
lines--;
|
||||
}
|
||||
html("</td>\n");
|
||||
}
|
||||
else
|
||||
html("<td/>"); /* Empty 'Age' column */
|
||||
|
||||
/* Print msgbuf into remainder of table row */
|
||||
htmlf("<td colspan='%d'%s>\n", columns - (revs->graph ? 1 : 0),
|
||||
ctx.qry.showmsg ? " class='logmsg'" : "");
|
||||
html_txt(msgbuf.buf);
|
||||
html("</td></tr>\n");
|
||||
}
|
||||
|
||||
strbuf_release(&msgbuf);
|
||||
strbuf_release(&graphbuf);
|
||||
cgit_free_commitinfo(info);
|
||||
}
|
||||
|
||||
static const char *disambiguate_ref(const char *ref, int *must_free_result)
|
||||
{
|
||||
struct object_id oid;
|
||||
struct strbuf longref = STRBUF_INIT;
|
||||
|
||||
strbuf_addf(&longref, "refs/heads/%s", ref);
|
||||
if (get_oid(longref.buf, &oid) == 0) {
|
||||
*must_free_result = 1;
|
||||
return strbuf_detach(&longref, NULL);
|
||||
}
|
||||
|
||||
*must_free_result = 0;
|
||||
strbuf_release(&longref);
|
||||
return ref;
|
||||
}
|
||||
|
||||
static char *next_token(char **src)
|
||||
{
|
||||
char *result;
|
||||
|
||||
if (!src || !*src)
|
||||
return NULL;
|
||||
while (isspace(**src))
|
||||
(*src)++;
|
||||
if (!**src)
|
||||
return NULL;
|
||||
result = *src;
|
||||
while (**src) {
|
||||
if (isspace(**src)) {
|
||||
**src = '\0';
|
||||
(*src)++;
|
||||
break;
|
||||
}
|
||||
(*src)++;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern,
|
||||
const char *path, int pager, int commit_graph, int commit_sort)
|
||||
{
|
||||
struct rev_info rev;
|
||||
struct commit *commit;
|
||||
struct argv_array rev_argv = ARGV_ARRAY_INIT;
|
||||
int i, columns = commit_graph ? 4 : 3;
|
||||
int must_free_tip = 0;
|
||||
|
||||
/* rev_argv.argv[0] will be ignored by setup_revisions */
|
||||
argv_array_push(&rev_argv, "log_rev_setup");
|
||||
|
||||
if (!tip)
|
||||
tip = ctx.qry.head;
|
||||
tip = disambiguate_ref(tip, &must_free_tip);
|
||||
argv_array_push(&rev_argv, tip);
|
||||
|
||||
if (grep && pattern && *pattern) {
|
||||
pattern = xstrdup(pattern);
|
||||
if (!strcmp(grep, "grep") || !strcmp(grep, "author") ||
|
||||
!strcmp(grep, "committer")) {
|
||||
argv_array_pushf(&rev_argv, "--%s=%s", grep, pattern);
|
||||
} else if (!strcmp(grep, "range")) {
|
||||
char *arg;
|
||||
/* Split the pattern at whitespace and add each token
|
||||
* as a revision expression. Do not accept other
|
||||
* rev-list options. Also, replace the previously
|
||||
* pushed tip (it's no longer relevant).
|
||||
*/
|
||||
argv_array_pop(&rev_argv);
|
||||
while ((arg = next_token(&pattern))) {
|
||||
if (*arg == '-') {
|
||||
fprintf(stderr, "Bad range expr: %s\n",
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
argv_array_push(&rev_argv, arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!path || !ctx.cfg.enable_follow_links) {
|
||||
/*
|
||||
* If we don't have a path, "follow" is a no-op so make sure
|
||||
* the variable is set to false to avoid needing to check
|
||||
* both this and whether we have a path everywhere.
|
||||
*/
|
||||
ctx.qry.follow = 0;
|
||||
}
|
||||
|
||||
if (commit_graph && !ctx.qry.follow) {
|
||||
argv_array_push(&rev_argv, "--graph");
|
||||
argv_array_push(&rev_argv, "--color");
|
||||
graph_set_column_colors(column_colors_html,
|
||||
COLUMN_COLORS_HTML_MAX);
|
||||
}
|
||||
|
||||
if (commit_sort == 1)
|
||||
argv_array_push(&rev_argv, "--date-order");
|
||||
else if (commit_sort == 2)
|
||||
argv_array_push(&rev_argv, "--topo-order");
|
||||
|
||||
if (path && ctx.qry.follow)
|
||||
argv_array_push(&rev_argv, "--follow");
|
||||
argv_array_push(&rev_argv, "--");
|
||||
if (path)
|
||||
argv_array_push(&rev_argv, path);
|
||||
|
||||
init_revisions(&rev, NULL);
|
||||
rev.abbrev = DEFAULT_ABBREV;
|
||||
rev.commit_format = CMIT_FMT_DEFAULT;
|
||||
rev.verbose_header = 1;
|
||||
rev.show_root_diff = 0;
|
||||
rev.ignore_missing = 1;
|
||||
rev.simplify_history = 1;
|
||||
setup_revisions(rev_argv.argc, rev_argv.argv, &rev, NULL);
|
||||
load_ref_decorations(NULL, DECORATE_FULL_REFS);
|
||||
rev.show_decorations = 1;
|
||||
rev.grep_filter.ignore_case = 1;
|
||||
|
||||
rev.diffopt.detect_rename = 1;
|
||||
rev.diffopt.rename_limit = ctx.cfg.renamelimit;
|
||||
if (ctx.qry.ignorews)
|
||||
DIFF_XDL_SET(&rev.diffopt, IGNORE_WHITESPACE);
|
||||
|
||||
compile_grep_patterns(&rev.grep_filter);
|
||||
prepare_revision_walk(&rev);
|
||||
|
||||
if (pager) {
|
||||
cgit_print_layout_start();
|
||||
html("<table class='list nowrap'>");
|
||||
}
|
||||
|
||||
html("<tr class='nohover'>");
|
||||
if (commit_graph)
|
||||
html("<th></th>");
|
||||
else
|
||||
html("<th class='left'>Age</th>");
|
||||
html("<th class='left'>Commit message");
|
||||
if (pager) {
|
||||
html(" (");
|
||||
cgit_log_link(ctx.qry.showmsg ? "Collapse" : "Expand", NULL,
|
||||
NULL, ctx.qry.head, ctx.qry.sha1,
|
||||
ctx.qry.vpath, ctx.qry.ofs, ctx.qry.grep,
|
||||
ctx.qry.search, ctx.qry.showmsg ? 0 : 1,
|
||||
ctx.qry.follow);
|
||||
html(")");
|
||||
}
|
||||
html("</th><th class='left'>Author</th>");
|
||||
if (rev.graph)
|
||||
html("<th class='left'>Age</th>");
|
||||
if (ctx.repo->enable_log_filecount) {
|
||||
html("<th class='left'>Files</th>");
|
||||
columns++;
|
||||
}
|
||||
if (ctx.repo->enable_log_linecount) {
|
||||
html("<th class='left'>Lines</th>");
|
||||
columns++;
|
||||
}
|
||||
html("</tr>\n");
|
||||
|
||||
if (ofs<0)
|
||||
ofs = 0;
|
||||
|
||||
for (i = 0; i < ofs && (commit = get_revision(&rev)) != NULL; /* nop */) {
|
||||
if (show_commit(commit, &rev))
|
||||
i++;
|
||||
free_commit_buffer(the_repository->parsed_objects, commit);
|
||||
free_commit_list(commit->parents);
|
||||
commit->parents = NULL;
|
||||
}
|
||||
|
||||
for (i = 0; i < cnt && (commit = get_revision(&rev)) != NULL; /* nop */) {
|
||||
/*
|
||||
* In "follow" mode, we must count the files and lines the
|
||||
* first time we invoke diff on a given commit, and we need
|
||||
* to do that to see if the commit touches the path we care
|
||||
* about, so we do it in show_commit. Hence we must clear
|
||||
* lines_counted here.
|
||||
*
|
||||
* This has the side effect of avoiding running diff twice
|
||||
* when we are both following renames and showing file
|
||||
* and/or line counts.
|
||||
*/
|
||||
lines_counted = 0;
|
||||
if (show_commit(commit, &rev)) {
|
||||
i++;
|
||||
print_commit(commit, &rev);
|
||||
}
|
||||
free_commit_buffer(the_repository->parsed_objects, commit);
|
||||
free_commit_list(commit->parents);
|
||||
commit->parents = NULL;
|
||||
}
|
||||
if (pager) {
|
||||
html("</table><ul class='pager'>");
|
||||
if (ofs > 0) {
|
||||
html("<li>");
|
||||
cgit_log_link("[prev]", NULL, NULL, ctx.qry.head,
|
||||
ctx.qry.sha1, ctx.qry.vpath,
|
||||
ofs - cnt, ctx.qry.grep,
|
||||
ctx.qry.search, ctx.qry.showmsg,
|
||||
ctx.qry.follow);
|
||||
html("</li>");
|
||||
}
|
||||
if ((commit = get_revision(&rev)) != NULL) {
|
||||
html("<li>");
|
||||
cgit_log_link("[next]", NULL, NULL, ctx.qry.head,
|
||||
ctx.qry.sha1, ctx.qry.vpath,
|
||||
ofs + cnt, ctx.qry.grep,
|
||||
ctx.qry.search, ctx.qry.showmsg,
|
||||
ctx.qry.follow);
|
||||
html("</li>");
|
||||
}
|
||||
html("</ul>");
|
||||
cgit_print_layout_end();
|
||||
} else if ((commit = get_revision(&rev)) != NULL) {
|
||||
htmlf("<tr class='nohover'><td colspan='%d'>", columns);
|
||||
cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL,
|
||||
ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg,
|
||||
ctx.qry.follow);
|
||||
html("</td></tr>\n");
|
||||
}
|
||||
|
||||
/* If we allocated tip then it is safe to cast away const. */
|
||||
if (must_free_tip)
|
||||
free((char*) tip);
|
||||
}
|
9
ui-log.h
Normal file
9
ui-log.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#ifndef UI_LOG_H
|
||||
#define UI_LOG_H
|
||||
|
||||
extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep,
|
||||
char *pattern, const char *path, int pager,
|
||||
int commit_graph, int commit_sort);
|
||||
extern void show_commit_decorations(struct commit *commit);
|
||||
|
||||
#endif /* UI_LOG_H */
|
98
ui-patch.c
Normal file
98
ui-patch.c
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* ui-patch.c: generate patch view
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-patch.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
/* two commit hashes with two dots in between and termination */
|
||||
#define REV_RANGE_LEN 2 * GIT_MAX_HEXSZ + 3
|
||||
|
||||
void cgit_print_patch(const char *new_rev, const char *old_rev,
|
||||
const char *prefix)
|
||||
{
|
||||
struct rev_info rev;
|
||||
struct commit *commit;
|
||||
struct object_id new_rev_oid, old_rev_oid;
|
||||
char rev_range[REV_RANGE_LEN];
|
||||
const char *rev_argv[] = { NULL, "--reverse", "--format=email", rev_range, "--", prefix, NULL };
|
||||
int rev_argc = ARRAY_SIZE(rev_argv) - 1;
|
||||
char *patchname;
|
||||
|
||||
if (!prefix)
|
||||
rev_argc--;
|
||||
|
||||
if (!new_rev)
|
||||
new_rev = ctx.qry.head;
|
||||
|
||||
if (get_oid(new_rev, &new_rev_oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad object id: %s", new_rev);
|
||||
return;
|
||||
}
|
||||
commit = lookup_commit_reference(the_repository, &new_rev_oid);
|
||||
if (!commit) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad commit reference: %s", new_rev);
|
||||
return;
|
||||
}
|
||||
|
||||
if (old_rev) {
|
||||
if (get_oid(old_rev, &old_rev_oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad object id: %s", old_rev);
|
||||
return;
|
||||
}
|
||||
if (!lookup_commit_reference(the_repository, &old_rev_oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad commit reference: %s", old_rev);
|
||||
return;
|
||||
}
|
||||
} else if (commit->parents && commit->parents->item) {
|
||||
oidcpy(&old_rev_oid, &commit->parents->item->object.oid);
|
||||
} else {
|
||||
oidclr(&old_rev_oid);
|
||||
}
|
||||
|
||||
if (is_null_oid(&old_rev_oid)) {
|
||||
memcpy(rev_range, oid_to_hex(&new_rev_oid), GIT_SHA1_HEXSZ + 1);
|
||||
} else {
|
||||
xsnprintf(rev_range, REV_RANGE_LEN, "%s..%s", oid_to_hex(&old_rev_oid),
|
||||
oid_to_hex(&new_rev_oid));
|
||||
}
|
||||
|
||||
patchname = fmt("%s.patch", rev_range);
|
||||
ctx.page.mimetype = "text/plain";
|
||||
ctx.page.filename = patchname;
|
||||
cgit_print_http_headers();
|
||||
|
||||
if (ctx.cfg.noplainemail) {
|
||||
rev_argv[2] = "--format=format:From %H Mon Sep 17 00:00:00 "
|
||||
"2001%nFrom: %an%nDate: %aD%n%w(78,0,1)Subject: "
|
||||
"%s%n%n%w(0)%b";
|
||||
}
|
||||
|
||||
init_revisions(&rev, NULL);
|
||||
rev.abbrev = DEFAULT_ABBREV;
|
||||
rev.verbose_header = 1;
|
||||
rev.diff = 1;
|
||||
rev.show_root_diff = 1;
|
||||
rev.max_parents = 1;
|
||||
rev.diffopt.output_format |= DIFF_FORMAT_DIFFSTAT |
|
||||
DIFF_FORMAT_PATCH | DIFF_FORMAT_SUMMARY;
|
||||
if (prefix)
|
||||
rev.diffopt.stat_sep = fmt("(limited to '%s')\n\n", prefix);
|
||||
setup_revisions(rev_argc, rev_argv, &rev, NULL);
|
||||
prepare_revision_walk(&rev);
|
||||
|
||||
while ((commit = get_revision(&rev)) != NULL) {
|
||||
log_tree_commit(&rev, commit);
|
||||
printf("-- \ncgit %s\n\n", cgit_version);
|
||||
}
|
||||
}
|
7
ui-patch.h
Normal file
7
ui-patch.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef UI_PATCH_H
|
||||
#define UI_PATCH_H
|
||||
|
||||
extern void cgit_print_patch(const char *new_rev, const char *old_rev,
|
||||
const char *prefix);
|
||||
|
||||
#endif /* UI_PATCH_H */
|
207
ui-plain.c
Normal file
207
ui-plain.c
Normal file
|
@ -0,0 +1,207 @@
|
|||
/* ui-plain.c: functions for output of plain blobs by path
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-plain.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
struct walk_tree_context {
|
||||
int match_baselen;
|
||||
int match;
|
||||
};
|
||||
|
||||
static int print_object(const struct object_id *oid, const char *path)
|
||||
{
|
||||
enum object_type type;
|
||||
char *buf, *mimetype;
|
||||
unsigned long size;
|
||||
|
||||
type = oid_object_info(the_repository, oid, &size);
|
||||
if (type == OBJ_BAD) {
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf = read_object_file(oid, &type, &size);
|
||||
if (!buf) {
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mimetype = get_mimetype_for_filename(path);
|
||||
ctx.page.mimetype = mimetype;
|
||||
|
||||
if (!ctx.repo->enable_html_serving) {
|
||||
html("X-Content-Type-Options: nosniff\n");
|
||||
html("Content-Security-Policy: default-src 'none'\n");
|
||||
if (mimetype) {
|
||||
/* Built-in white list allows PDF and everything that isn't text/ and application/ */
|
||||
if ((!strncmp(mimetype, "text/", 5) || !strncmp(mimetype, "application/", 12)) && strcmp(mimetype, "application/pdf"))
|
||||
ctx.page.mimetype = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.page.mimetype) {
|
||||
if (buffer_is_binary(buf, size)) {
|
||||
ctx.page.mimetype = "application/octet-stream";
|
||||
ctx.page.charset = NULL;
|
||||
} else {
|
||||
ctx.page.mimetype = "text/plain";
|
||||
}
|
||||
}
|
||||
ctx.page.filename = path;
|
||||
ctx.page.size = size;
|
||||
ctx.page.etag = oid_to_hex(oid);
|
||||
cgit_print_http_headers();
|
||||
html_raw(buf, size);
|
||||
free(mimetype);
|
||||
free(buf);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static char *buildpath(const char *base, int baselen, const char *path)
|
||||
{
|
||||
if (path[0])
|
||||
return fmtalloc("%.*s%s/", baselen, base, path);
|
||||
else
|
||||
return fmtalloc("%.*s/", baselen, base);
|
||||
}
|
||||
|
||||
static void print_dir(const struct object_id *oid, const char *base,
|
||||
int baselen, const char *path)
|
||||
{
|
||||
char *fullpath, *slash;
|
||||
size_t len;
|
||||
|
||||
fullpath = buildpath(base, baselen, path);
|
||||
slash = (fullpath[0] == '/' ? "" : "/");
|
||||
ctx.page.etag = oid_to_hex(oid);
|
||||
cgit_print_http_headers();
|
||||
htmlf("<html><head><title>%s", slash);
|
||||
html_txt(fullpath);
|
||||
htmlf("</title></head>\n<body>\n<h2>%s", slash);
|
||||
html_txt(fullpath);
|
||||
html("</h2>\n<ul>\n");
|
||||
len = strlen(fullpath);
|
||||
if (len > 1) {
|
||||
fullpath[len - 1] = 0;
|
||||
slash = strrchr(fullpath, '/');
|
||||
if (slash)
|
||||
*(slash + 1) = 0;
|
||||
else {
|
||||
free(fullpath);
|
||||
fullpath = NULL;
|
||||
}
|
||||
html("<li>");
|
||||
cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.sha1,
|
||||
fullpath);
|
||||
html("</li>\n");
|
||||
}
|
||||
free(fullpath);
|
||||
}
|
||||
|
||||
static void print_dir_entry(const struct object_id *oid, const char *base,
|
||||
int baselen, const char *path, unsigned mode)
|
||||
{
|
||||
char *fullpath;
|
||||
|
||||
fullpath = buildpath(base, baselen, path);
|
||||
if (!S_ISDIR(mode) && !S_ISGITLINK(mode))
|
||||
fullpath[strlen(fullpath) - 1] = 0;
|
||||
html(" <li>");
|
||||
if (S_ISGITLINK(mode)) {
|
||||
cgit_submodule_link(NULL, fullpath, oid_to_hex(oid));
|
||||
} else
|
||||
cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1,
|
||||
fullpath);
|
||||
html("</li>\n");
|
||||
free(fullpath);
|
||||
}
|
||||
|
||||
static void print_dir_tail(void)
|
||||
{
|
||||
html(" </ul>\n</body></html>\n");
|
||||
}
|
||||
|
||||
static int walk_tree(const struct object_id *oid, struct strbuf *base,
|
||||
const char *pathname, unsigned mode, int stage, void *cbdata)
|
||||
{
|
||||
struct walk_tree_context *walk_tree_ctx = cbdata;
|
||||
|
||||
if (base->len == walk_tree_ctx->match_baselen) {
|
||||
if (S_ISREG(mode) || S_ISLNK(mode)) {
|
||||
if (print_object(oid, pathname))
|
||||
walk_tree_ctx->match = 1;
|
||||
} else if (S_ISDIR(mode)) {
|
||||
print_dir(oid, base->buf, base->len, pathname);
|
||||
walk_tree_ctx->match = 2;
|
||||
return READ_TREE_RECURSIVE;
|
||||
}
|
||||
} else if (base->len < INT_MAX && (int)base->len > walk_tree_ctx->match_baselen) {
|
||||
print_dir_entry(oid, base->buf, base->len, pathname, mode);
|
||||
walk_tree_ctx->match = 2;
|
||||
} else if (S_ISDIR(mode)) {
|
||||
return READ_TREE_RECURSIVE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int basedir_len(const char *path)
|
||||
{
|
||||
char *p = strrchr(path, '/');
|
||||
if (p)
|
||||
return p - path + 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cgit_print_plain(void)
|
||||
{
|
||||
const char *rev = ctx.qry.sha1;
|
||||
struct object_id oid;
|
||||
struct commit *commit;
|
||||
struct pathspec_item path_items = {
|
||||
.match = ctx.qry.path,
|
||||
.len = ctx.qry.path ? strlen(ctx.qry.path) : 0
|
||||
};
|
||||
struct pathspec paths = {
|
||||
.nr = 1,
|
||||
.items = &path_items
|
||||
};
|
||||
struct walk_tree_context walk_tree_ctx = {
|
||||
.match = 0
|
||||
};
|
||||
|
||||
if (!rev)
|
||||
rev = ctx.qry.head;
|
||||
|
||||
if (get_oid(rev, &oid)) {
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
return;
|
||||
}
|
||||
commit = lookup_commit_reference(the_repository, &oid);
|
||||
if (!commit || parse_commit(commit)) {
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
return;
|
||||
}
|
||||
if (!path_items.match) {
|
||||
path_items.match = "";
|
||||
walk_tree_ctx.match_baselen = -1;
|
||||
print_dir(&commit->maybe_tree->object.oid, "", 0, "");
|
||||
walk_tree_ctx.match = 2;
|
||||
}
|
||||
else
|
||||
walk_tree_ctx.match_baselen = basedir_len(path_items.match);
|
||||
read_tree_recursive(the_repository, commit->maybe_tree,
|
||||
"", 0, 0, &paths, walk_tree, &walk_tree_ctx);
|
||||
if (!walk_tree_ctx.match)
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
else if (walk_tree_ctx.match == 2)
|
||||
print_dir_tail();
|
||||
}
|
6
ui-plain.h
Normal file
6
ui-plain.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef UI_PLAIN_H
|
||||
#define UI_PLAIN_H
|
||||
|
||||
extern void cgit_print_plain(void);
|
||||
|
||||
#endif /* UI_PLAIN_H */
|
219
ui-refs.c
Normal file
219
ui-refs.c
Normal file
|
@ -0,0 +1,219 @@
|
|||
/* ui-refs.c: browse symbolic refs
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-refs.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
static inline int cmp_age(int age1, int age2)
|
||||
{
|
||||
/* age1 and age2 are assumed to be non-negative */
|
||||
return age2 - age1;
|
||||
}
|
||||
|
||||
static int cmp_ref_name(const void *a, const void *b)
|
||||
{
|
||||
struct refinfo *r1 = *(struct refinfo **)a;
|
||||
struct refinfo *r2 = *(struct refinfo **)b;
|
||||
|
||||
return strcmp(r1->refname, r2->refname);
|
||||
}
|
||||
|
||||
static int cmp_branch_age(const void *a, const void *b)
|
||||
{
|
||||
struct refinfo *r1 = *(struct refinfo **)a;
|
||||
struct refinfo *r2 = *(struct refinfo **)b;
|
||||
|
||||
return cmp_age(r1->commit->committer_date, r2->commit->committer_date);
|
||||
}
|
||||
|
||||
static int get_ref_age(struct refinfo *ref)
|
||||
{
|
||||
if (!ref->object)
|
||||
return 0;
|
||||
switch (ref->object->type) {
|
||||
case OBJ_TAG:
|
||||
return ref->tag ? ref->tag->tagger_date : 0;
|
||||
case OBJ_COMMIT:
|
||||
return ref->commit ? ref->commit->committer_date : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int cmp_tag_age(const void *a, const void *b)
|
||||
{
|
||||
struct refinfo *r1 = *(struct refinfo **)a;
|
||||
struct refinfo *r2 = *(struct refinfo **)b;
|
||||
|
||||
return cmp_age(get_ref_age(r1), get_ref_age(r2));
|
||||
}
|
||||
|
||||
static int print_branch(struct refinfo *ref)
|
||||
{
|
||||
struct commitinfo *info = ref->commit;
|
||||
char *name = (char *)ref->refname;
|
||||
|
||||
if (!info)
|
||||
return 1;
|
||||
html("<tr><td>");
|
||||
cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL,
|
||||
ctx.qry.showmsg, 0);
|
||||
html("</td><td>");
|
||||
|
||||
if (ref->object->type == OBJ_COMMIT) {
|
||||
cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL);
|
||||
html("</td><td>");
|
||||
cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs");
|
||||
html_txt(info->author);
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
html("</td><td colspan='2'>");
|
||||
cgit_print_age(info->committer_date, info->committer_tz, -1);
|
||||
} else {
|
||||
html("</td><td></td><td>");
|
||||
cgit_object_link(ref->object);
|
||||
}
|
||||
html("</td></tr>\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_tag_header(void)
|
||||
{
|
||||
html("<tr class='nohover'><th class='left'>Tag</th>"
|
||||
"<th class='left'>Download</th>"
|
||||
"<th class='left'>Author</th>"
|
||||
"<th class='left' colspan='2'>Age</th></tr>\n");
|
||||
}
|
||||
|
||||
static int print_tag(struct refinfo *ref)
|
||||
{
|
||||
struct tag *tag = NULL;
|
||||
struct taginfo *info = NULL;
|
||||
char *name = (char *)ref->refname;
|
||||
struct object *obj = ref->object;
|
||||
|
||||
if (obj->type == OBJ_TAG) {
|
||||
tag = (struct tag *)obj;
|
||||
obj = tag->tagged;
|
||||
info = ref->tag;
|
||||
if (!info)
|
||||
return 1;
|
||||
}
|
||||
|
||||
html("<tr><td>");
|
||||
cgit_tag_link(name, NULL, NULL, name);
|
||||
html("</td><td>");
|
||||
if (ctx.repo->snapshots && (obj->type == OBJ_COMMIT))
|
||||
cgit_print_snapshot_links(ctx.repo, name, " ");
|
||||
else
|
||||
cgit_object_link(obj);
|
||||
html("</td><td>");
|
||||
if (info) {
|
||||
if (info->tagger) {
|
||||
cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "refs");
|
||||
html_txt(info->tagger);
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
}
|
||||
} else if (ref->object->type == OBJ_COMMIT) {
|
||||
cgit_open_filter(ctx.repo->email_filter, ref->commit->author_email, "refs");
|
||||
html_txt(ref->commit->author);
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
}
|
||||
html("</td><td colspan='2'>");
|
||||
if (info) {
|
||||
if (info->tagger_date > 0)
|
||||
cgit_print_age(info->tagger_date, info->tagger_tz, -1);
|
||||
} else if (ref->object->type == OBJ_COMMIT) {
|
||||
cgit_print_age(ref->commit->commit->date, 0, -1);
|
||||
}
|
||||
html("</td></tr>\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_refs_link(const char *path)
|
||||
{
|
||||
html("<tr class='nohover'><td colspan='5'>");
|
||||
cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path);
|
||||
html("</td></tr>");
|
||||
}
|
||||
|
||||
void cgit_print_branches(int maxcount)
|
||||
{
|
||||
struct reflist list;
|
||||
int i;
|
||||
|
||||
html("<tr class='nohover'><th class='left'>Branch</th>"
|
||||
"<th class='left'>Commit message</th>"
|
||||
"<th class='left'>Author</th>"
|
||||
"<th class='left' colspan='2'>Age</th></tr>\n");
|
||||
|
||||
list.refs = NULL;
|
||||
list.alloc = list.count = 0;
|
||||
for_each_branch_ref(cgit_refs_cb, &list);
|
||||
if (ctx.repo->enable_remote_branches)
|
||||
for_each_remote_ref(cgit_refs_cb, &list);
|
||||
|
||||
if (maxcount == 0 || maxcount > list.count)
|
||||
maxcount = list.count;
|
||||
|
||||
qsort(list.refs, list.count, sizeof(*list.refs), cmp_branch_age);
|
||||
if (ctx.repo->branch_sort == 0)
|
||||
qsort(list.refs, maxcount, sizeof(*list.refs), cmp_ref_name);
|
||||
|
||||
for (i = 0; i < maxcount; i++)
|
||||
print_branch(list.refs[i]);
|
||||
|
||||
if (maxcount < list.count)
|
||||
print_refs_link("heads");
|
||||
|
||||
cgit_free_reflist_inner(&list);
|
||||
}
|
||||
|
||||
void cgit_print_tags(int maxcount)
|
||||
{
|
||||
struct reflist list;
|
||||
int i;
|
||||
|
||||
list.refs = NULL;
|
||||
list.alloc = list.count = 0;
|
||||
for_each_tag_ref(cgit_refs_cb, &list);
|
||||
if (list.count == 0)
|
||||
return;
|
||||
qsort(list.refs, list.count, sizeof(*list.refs), cmp_tag_age);
|
||||
if (!maxcount)
|
||||
maxcount = list.count;
|
||||
else if (maxcount > list.count)
|
||||
maxcount = list.count;
|
||||
print_tag_header();
|
||||
for (i = 0; i < maxcount; i++)
|
||||
print_tag(list.refs[i]);
|
||||
|
||||
if (maxcount < list.count)
|
||||
print_refs_link("tags");
|
||||
|
||||
cgit_free_reflist_inner(&list);
|
||||
}
|
||||
|
||||
void cgit_print_refs(void)
|
||||
{
|
||||
cgit_print_layout_start();
|
||||
html("<table class='list nowrap'>");
|
||||
|
||||
if (ctx.qry.path && starts_with(ctx.qry.path, "heads"))
|
||||
cgit_print_branches(0);
|
||||
else if (ctx.qry.path && starts_with(ctx.qry.path, "tags"))
|
||||
cgit_print_tags(0);
|
||||
else {
|
||||
cgit_print_branches(0);
|
||||
html("<tr class='nohover'><td colspan='5'> </td></tr>");
|
||||
cgit_print_tags(0);
|
||||
}
|
||||
html("</table>");
|
||||
cgit_print_layout_end();
|
||||
}
|
8
ui-refs.h
Normal file
8
ui-refs.h
Normal file
|
@ -0,0 +1,8 @@
|
|||
#ifndef UI_REFS_H
|
||||
#define UI_REFS_H
|
||||
|
||||
extern void cgit_print_branches(int maxcount);
|
||||
extern void cgit_print_tags(int maxcount);
|
||||
extern void cgit_print_refs(void);
|
||||
|
||||
#endif /* UI_REFS_H */
|
379
ui-repolist.c
Normal file
379
ui-repolist.c
Normal file
|
@ -0,0 +1,379 @@
|
|||
/* ui-repolist.c: functions for generating the repolist page
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-repolist.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
static time_t read_agefile(const char *path)
|
||||
{
|
||||
time_t result;
|
||||
size_t size;
|
||||
char *buf = NULL;
|
||||
struct strbuf date_buf = STRBUF_INIT;
|
||||
|
||||
if (readfile(path, &buf, &size)) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (parse_date(buf, &date_buf) == 0)
|
||||
result = strtoul(date_buf.buf, NULL, 10);
|
||||
else
|
||||
result = 0;
|
||||
free(buf);
|
||||
strbuf_release(&date_buf);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int get_repo_modtime(const struct cgit_repo *repo, time_t *mtime)
|
||||
{
|
||||
struct strbuf path = STRBUF_INIT;
|
||||
struct stat s;
|
||||
struct cgit_repo *r = (struct cgit_repo *)repo;
|
||||
|
||||
if (repo->mtime != -1) {
|
||||
*mtime = repo->mtime;
|
||||
return 1;
|
||||
}
|
||||
strbuf_addf(&path, "%s/%s", repo->path, ctx.cfg.agefile);
|
||||
if (stat(path.buf, &s) == 0) {
|
||||
*mtime = read_agefile(path.buf);
|
||||
if (*mtime) {
|
||||
r->mtime = *mtime;
|
||||
goto end;
|
||||
}
|
||||
}
|
||||
|
||||
strbuf_reset(&path);
|
||||
strbuf_addf(&path, "%s/refs/heads/%s", repo->path,
|
||||
repo->defbranch ? repo->defbranch : "master");
|
||||
if (stat(path.buf, &s) == 0) {
|
||||
*mtime = s.st_mtime;
|
||||
r->mtime = *mtime;
|
||||
goto end;
|
||||
}
|
||||
|
||||
strbuf_reset(&path);
|
||||
strbuf_addf(&path, "%s/%s", repo->path, "packed-refs");
|
||||
if (stat(path.buf, &s) == 0) {
|
||||
*mtime = s.st_mtime;
|
||||
r->mtime = *mtime;
|
||||
goto end;
|
||||
}
|
||||
|
||||
*mtime = 0;
|
||||
r->mtime = *mtime;
|
||||
end:
|
||||
strbuf_release(&path);
|
||||
return (r->mtime != 0);
|
||||
}
|
||||
|
||||
static void print_modtime(struct cgit_repo *repo)
|
||||
{
|
||||
time_t t;
|
||||
if (get_repo_modtime(repo, &t))
|
||||
cgit_print_age(t, 0, -1);
|
||||
}
|
||||
|
||||
static int is_match(struct cgit_repo *repo)
|
||||
{
|
||||
if (!ctx.qry.search)
|
||||
return 1;
|
||||
if (repo->url && strcasestr(repo->url, ctx.qry.search))
|
||||
return 1;
|
||||
if (repo->name && strcasestr(repo->name, ctx.qry.search))
|
||||
return 1;
|
||||
if (repo->desc && strcasestr(repo->desc, ctx.qry.search))
|
||||
return 1;
|
||||
if (repo->owner && strcasestr(repo->owner, ctx.qry.search))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_in_url(struct cgit_repo *repo)
|
||||
{
|
||||
if (!ctx.qry.url)
|
||||
return 1;
|
||||
if (repo->url && starts_with(repo->url, ctx.qry.url))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int is_visible(struct cgit_repo *repo)
|
||||
{
|
||||
if (repo->hide || repo->ignore)
|
||||
return 0;
|
||||
if (!(is_match(repo) && is_in_url(repo)))
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int any_repos_visible(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < cgit_repolist.count; i++) {
|
||||
if (is_visible(&cgit_repolist.repos[i]))
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void print_sort_header(const char *title, const char *sort)
|
||||
{
|
||||
char *currenturl = cgit_currenturl();
|
||||
html("<th class='left'><a href='");
|
||||
html_attr(currenturl);
|
||||
htmlf("?s=%s", sort);
|
||||
if (ctx.qry.search) {
|
||||
html("&q=");
|
||||
html_url_arg(ctx.qry.search);
|
||||
}
|
||||
htmlf("'>%s</a></th>", title);
|
||||
free(currenturl);
|
||||
}
|
||||
|
||||
static void print_header(void)
|
||||
{
|
||||
html("<tr class='nohover'>");
|
||||
print_sort_header("Name", "name");
|
||||
print_sort_header("Description", "desc");
|
||||
if (ctx.cfg.enable_index_owner)
|
||||
print_sort_header("Owner", "owner");
|
||||
print_sort_header("Idle", "idle");
|
||||
if (ctx.cfg.enable_index_links)
|
||||
html("<th class='left'>Links</th>");
|
||||
html("</tr>\n");
|
||||
}
|
||||
|
||||
|
||||
static void print_pager(int items, int pagelen, char *search, char *sort)
|
||||
{
|
||||
int i, ofs;
|
||||
char *class = NULL;
|
||||
html("<ul class='pager'>");
|
||||
for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) {
|
||||
class = (ctx.qry.ofs == ofs) ? "current" : NULL;
|
||||
html("<li>");
|
||||
cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1),
|
||||
class, search, sort, ofs, 0);
|
||||
html("</li>");
|
||||
}
|
||||
html("</ul>");
|
||||
}
|
||||
|
||||
static int cmp(const char *s1, const char *s2)
|
||||
{
|
||||
if (s1 && s2) {
|
||||
if (ctx.cfg.case_sensitive_sort)
|
||||
return strcmp(s1, s2);
|
||||
else
|
||||
return strcasecmp(s1, s2);
|
||||
}
|
||||
if (s1 && !s2)
|
||||
return -1;
|
||||
if (s2 && !s1)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sort_name(const void *a, const void *b)
|
||||
{
|
||||
const struct cgit_repo *r1 = a;
|
||||
const struct cgit_repo *r2 = b;
|
||||
|
||||
return cmp(r1->name, r2->name);
|
||||
}
|
||||
|
||||
static int sort_desc(const void *a, const void *b)
|
||||
{
|
||||
const struct cgit_repo *r1 = a;
|
||||
const struct cgit_repo *r2 = b;
|
||||
|
||||
return cmp(r1->desc, r2->desc);
|
||||
}
|
||||
|
||||
static int sort_owner(const void *a, const void *b)
|
||||
{
|
||||
const struct cgit_repo *r1 = a;
|
||||
const struct cgit_repo *r2 = b;
|
||||
|
||||
return cmp(r1->owner, r2->owner);
|
||||
}
|
||||
|
||||
static int sort_idle(const void *a, const void *b)
|
||||
{
|
||||
const struct cgit_repo *r1 = a;
|
||||
const struct cgit_repo *r2 = b;
|
||||
time_t t1, t2;
|
||||
|
||||
t1 = t2 = 0;
|
||||
get_repo_modtime(r1, &t1);
|
||||
get_repo_modtime(r2, &t2);
|
||||
return t2 - t1;
|
||||
}
|
||||
|
||||
static int sort_section(const void *a, const void *b)
|
||||
{
|
||||
const struct cgit_repo *r1 = a;
|
||||
const struct cgit_repo *r2 = b;
|
||||
int result;
|
||||
|
||||
result = cmp(r1->section, r2->section);
|
||||
if (!result) {
|
||||
if (!strcmp(ctx.cfg.repository_sort, "age"))
|
||||
result = sort_idle(r1, r2);
|
||||
if (!result)
|
||||
result = cmp(r1->name, r2->name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
struct sortcolumn {
|
||||
const char *name;
|
||||
int (*fn)(const void *a, const void *b);
|
||||
};
|
||||
|
||||
static const struct sortcolumn sortcolumn[] = {
|
||||
{"section", sort_section},
|
||||
{"name", sort_name},
|
||||
{"desc", sort_desc},
|
||||
{"owner", sort_owner},
|
||||
{"idle", sort_idle},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
static int sort_repolist(char *field)
|
||||
{
|
||||
const struct sortcolumn *column;
|
||||
|
||||
for (column = &sortcolumn[0]; column->name; column++) {
|
||||
if (strcmp(field, column->name))
|
||||
continue;
|
||||
qsort(cgit_repolist.repos, cgit_repolist.count,
|
||||
sizeof(struct cgit_repo), column->fn);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
void cgit_print_repolist(void)
|
||||
{
|
||||
int i, columns = 3, hits = 0, header = 0;
|
||||
char *last_section = NULL;
|
||||
char *section;
|
||||
char *repourl;
|
||||
int sorted = 0;
|
||||
|
||||
if (!any_repos_visible()) {
|
||||
cgit_print_error_page(404, "Not found", "No repositories found");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ctx.cfg.enable_index_links)
|
||||
++columns;
|
||||
if (ctx.cfg.enable_index_owner)
|
||||
++columns;
|
||||
|
||||
ctx.page.title = ctx.cfg.root_title;
|
||||
cgit_print_http_headers();
|
||||
cgit_print_docstart();
|
||||
cgit_print_pageheader();
|
||||
|
||||
if (ctx.qry.sort)
|
||||
sorted = sort_repolist(ctx.qry.sort);
|
||||
else if (ctx.cfg.section_sort)
|
||||
sort_repolist("section");
|
||||
|
||||
html("<table summary='repository list' class='list nowrap'>");
|
||||
for (i = 0; i < cgit_repolist.count; i++) {
|
||||
ctx.repo = &cgit_repolist.repos[i];
|
||||
if (!is_visible(ctx.repo))
|
||||
continue;
|
||||
hits++;
|
||||
if (hits <= ctx.qry.ofs)
|
||||
continue;
|
||||
if (hits > ctx.qry.ofs + ctx.cfg.max_repo_count)
|
||||
continue;
|
||||
if (!header++)
|
||||
print_header();
|
||||
section = ctx.repo->section;
|
||||
if (section && !strcmp(section, ""))
|
||||
section = NULL;
|
||||
if (!sorted &&
|
||||
((last_section == NULL && section != NULL) ||
|
||||
(last_section != NULL && section == NULL) ||
|
||||
(last_section != NULL && section != NULL &&
|
||||
strcmp(section, last_section)))) {
|
||||
htmlf("<tr class='nohover-highlight'><td colspan='%d' class='reposection'>",
|
||||
columns);
|
||||
html_txt(section);
|
||||
html("</td></tr>");
|
||||
last_section = section;
|
||||
}
|
||||
htmlf("<tr><td class='%s'>",
|
||||
!sorted && section ? "sublevel-repo" : "toplevel-repo");
|
||||
cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL);
|
||||
html("</td><td>");
|
||||
repourl = cgit_repourl(ctx.repo->url);
|
||||
html_link_open(repourl, NULL, NULL);
|
||||
free(repourl);
|
||||
if (html_ntxt(ctx.repo->desc, ctx.cfg.max_repodesc_len) < 0)
|
||||
html("...");
|
||||
html_link_close();
|
||||
html("</td><td>");
|
||||
if (ctx.cfg.enable_index_owner) {
|
||||
if (ctx.repo->owner_filter) {
|
||||
cgit_open_filter(ctx.repo->owner_filter);
|
||||
html_txt(ctx.repo->owner);
|
||||
cgit_close_filter(ctx.repo->owner_filter);
|
||||
} else {
|
||||
char *currenturl = cgit_currenturl();
|
||||
html("<a href='");
|
||||
html_attr(currenturl);
|
||||
html("?q=");
|
||||
html_url_arg(ctx.repo->owner);
|
||||
html("'>");
|
||||
html_txt(ctx.repo->owner);
|
||||
html("</a>");
|
||||
free(currenturl);
|
||||
}
|
||||
html("</td><td>");
|
||||
}
|
||||
print_modtime(ctx.repo);
|
||||
html("</td>");
|
||||
if (ctx.cfg.enable_index_links) {
|
||||
html("<td>");
|
||||
cgit_summary_link("summary", NULL, "button", NULL);
|
||||
cgit_log_link("log", NULL, "button", NULL, NULL, NULL,
|
||||
0, NULL, NULL, ctx.qry.showmsg, 0);
|
||||
cgit_tree_link("tree", NULL, "button", NULL, NULL, NULL);
|
||||
html("</td>");
|
||||
}
|
||||
html("</tr>\n");
|
||||
}
|
||||
html("</table>");
|
||||
if (hits > ctx.cfg.max_repo_count)
|
||||
print_pager(hits, ctx.cfg.max_repo_count, ctx.qry.search, ctx.qry.sort);
|
||||
cgit_print_docend();
|
||||
}
|
||||
|
||||
void cgit_print_site_readme(void)
|
||||
{
|
||||
cgit_print_layout_start();
|
||||
if (!ctx.cfg.root_readme)
|
||||
goto done;
|
||||
cgit_open_filter(ctx.cfg.about_filter, ctx.cfg.root_readme);
|
||||
html_include(ctx.cfg.root_readme);
|
||||
cgit_close_filter(ctx.cfg.about_filter);
|
||||
done:
|
||||
cgit_print_layout_end();
|
||||
}
|
7
ui-repolist.h
Normal file
7
ui-repolist.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef UI_REPOLIST_H
|
||||
#define UI_REPOLIST_H
|
||||
|
||||
extern void cgit_print_repolist(void);
|
||||
extern void cgit_print_site_readme(void);
|
||||
|
||||
#endif /* UI_REPOLIST_H */
|
1210
ui-shared.c
Normal file
1210
ui-shared.c
Normal file
File diff suppressed because it is too large
Load diff
87
ui-shared.h
Normal file
87
ui-shared.h
Normal file
|
@ -0,0 +1,87 @@
|
|||
#ifndef UI_SHARED_H
|
||||
#define UI_SHARED_H
|
||||
|
||||
extern const char *cgit_httpscheme(void);
|
||||
extern char *cgit_hosturl(void);
|
||||
extern const char *cgit_rooturl(void);
|
||||
extern char *cgit_currenturl(void);
|
||||
extern char *cgit_currentfullurl(void);
|
||||
extern const char *cgit_loginurl(void);
|
||||
extern char *cgit_repourl(const char *reponame);
|
||||
extern char *cgit_fileurl(const char *reponame, const char *pagename,
|
||||
const char *filename, const char *query);
|
||||
extern char *cgit_pageurl(const char *reponame, const char *pagename,
|
||||
const char *query);
|
||||
|
||||
extern void cgit_add_clone_urls(void (*fn)(const char *));
|
||||
|
||||
extern void cgit_index_link(const char *name, const char *title,
|
||||
const char *class, const char *pattern, const char *sort, int ofs, int always_root);
|
||||
extern void cgit_summary_link(const char *name, const char *title,
|
||||
const char *class, const char *head);
|
||||
extern void cgit_tag_link(const char *name, const char *title,
|
||||
const char *class, const char *tag);
|
||||
extern void cgit_tree_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *path);
|
||||
extern void cgit_plain_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *path);
|
||||
extern void cgit_blame_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *path);
|
||||
extern void cgit_log_link(const char *name, const char *title,
|
||||
const char *class, const char *head, const char *rev,
|
||||
const char *path, int ofs, const char *grep,
|
||||
const char *pattern, int showmsg, int follow);
|
||||
extern void cgit_commit_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *path);
|
||||
extern void cgit_patch_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *path);
|
||||
extern void cgit_refs_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *path);
|
||||
extern void cgit_snapshot_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *rev, const char *archivename);
|
||||
extern void cgit_diff_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *new_rev, const char *old_rev,
|
||||
const char *path);
|
||||
extern void cgit_stats_link(const char *name, const char *title,
|
||||
const char *class, const char *head,
|
||||
const char *path);
|
||||
extern void cgit_object_link(struct object *obj);
|
||||
|
||||
extern void cgit_submodule_link(const char *class, char *path,
|
||||
const char *rev);
|
||||
|
||||
extern void cgit_print_layout_start(void);
|
||||
extern void cgit_print_layout_end(void);
|
||||
|
||||
__attribute__((format (printf,1,2)))
|
||||
extern void cgit_print_error(const char *fmt, ...);
|
||||
__attribute__((format (printf,1,0)))
|
||||
extern void cgit_vprint_error(const char *fmt, va_list ap);
|
||||
extern const struct date_mode *cgit_date_mode(enum date_mode_type type);
|
||||
extern void cgit_print_age(time_t t, int tz, time_t max_relative);
|
||||
extern void cgit_print_http_headers(void);
|
||||
extern void cgit_redirect(const char *url, bool permanent);
|
||||
extern void cgit_print_docstart(void);
|
||||
extern void cgit_print_docend(void);
|
||||
__attribute__((format (printf,3,4)))
|
||||
extern void cgit_print_error_page(int code, const char *msg, const char *fmt, ...);
|
||||
extern void cgit_print_pageheader(void);
|
||||
extern void cgit_print_filemode(unsigned short mode);
|
||||
extern void cgit_compose_snapshot_prefix(struct strbuf *filename,
|
||||
const char *base, const char *ref);
|
||||
extern void cgit_print_snapshot_links(const struct cgit_repo *repo,
|
||||
const char *ref, const char *separator);
|
||||
extern const char *cgit_snapshot_prefix(const struct cgit_repo *repo);
|
||||
extern void cgit_add_hidden_formfields(int incl_head, int incl_search,
|
||||
const char *page);
|
||||
|
||||
extern void cgit_set_title_from_path(const char *path);
|
||||
#endif /* UI_SHARED_H */
|
302
ui-snapshot.c
Normal file
302
ui-snapshot.c
Normal file
|
@ -0,0 +1,302 @@
|
|||
/* ui-snapshot.c: generate snapshot of a commit
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-snapshot.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
static int write_archive_type(const char *format, const char *hex, const char *prefix)
|
||||
{
|
||||
struct argv_array argv = ARGV_ARRAY_INIT;
|
||||
const char **nargv;
|
||||
int result;
|
||||
argv_array_push(&argv, "snapshot");
|
||||
argv_array_push(&argv, format);
|
||||
if (prefix) {
|
||||
struct strbuf buf = STRBUF_INIT;
|
||||
strbuf_addstr(&buf, prefix);
|
||||
strbuf_addch(&buf, '/');
|
||||
argv_array_push(&argv, "--prefix");
|
||||
argv_array_push(&argv, buf.buf);
|
||||
strbuf_release(&buf);
|
||||
}
|
||||
argv_array_push(&argv, hex);
|
||||
/*
|
||||
* Now we need to copy the pointers to arguments into a new
|
||||
* structure because write_archive will rearrange its arguments
|
||||
* which may result in duplicated/missing entries causing leaks
|
||||
* or double-frees in argv_array_clear.
|
||||
*/
|
||||
nargv = xmalloc(sizeof(char *) * (argv.argc + 1));
|
||||
/* argv_array guarantees a trailing NULL entry. */
|
||||
memcpy(nargv, argv.argv, sizeof(char *) * (argv.argc + 1));
|
||||
|
||||
result = write_archive(argv.argc, nargv, NULL, the_repository, NULL, 0);
|
||||
argv_array_clear(&argv);
|
||||
free(nargv);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int write_tar_archive(const char *hex, const char *prefix)
|
||||
{
|
||||
return write_archive_type("--format=tar", hex, prefix);
|
||||
}
|
||||
|
||||
static int write_zip_archive(const char *hex, const char *prefix)
|
||||
{
|
||||
return write_archive_type("--format=zip", hex, prefix);
|
||||
}
|
||||
|
||||
static int write_compressed_tar_archive(const char *hex,
|
||||
const char *prefix,
|
||||
char *filter_argv[])
|
||||
{
|
||||
int rv;
|
||||
struct cgit_exec_filter f;
|
||||
cgit_exec_filter_init(&f, filter_argv[0], filter_argv);
|
||||
|
||||
cgit_open_filter(&f.base);
|
||||
rv = write_tar_archive(hex, prefix);
|
||||
cgit_close_filter(&f.base);
|
||||
return rv;
|
||||
}
|
||||
|
||||
static int write_tar_gzip_archive(const char *hex, const char *prefix)
|
||||
{
|
||||
char *argv[] = { "gzip", "-n", NULL };
|
||||
return write_compressed_tar_archive(hex, prefix, argv);
|
||||
}
|
||||
|
||||
static int write_tar_bzip2_archive(const char *hex, const char *prefix)
|
||||
{
|
||||
char *argv[] = { "bzip2", NULL };
|
||||
return write_compressed_tar_archive(hex, prefix, argv);
|
||||
}
|
||||
|
||||
static int write_tar_xz_archive(const char *hex, const char *prefix)
|
||||
{
|
||||
char *argv[] = { "xz", NULL };
|
||||
return write_compressed_tar_archive(hex, prefix, argv);
|
||||
}
|
||||
|
||||
const struct cgit_snapshot_format cgit_snapshot_formats[] = {
|
||||
/* .tar must remain the 0 index */
|
||||
{ ".tar", "application/x-tar", write_tar_archive },
|
||||
{ ".tar.gz", "application/x-gzip", write_tar_gzip_archive },
|
||||
{ ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive },
|
||||
{ ".tar.xz", "application/x-xz", write_tar_xz_archive },
|
||||
{ ".zip", "application/x-zip", write_zip_archive },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
static struct notes_tree snapshot_sig_notes[ARRAY_SIZE(cgit_snapshot_formats)];
|
||||
|
||||
const struct object_id *cgit_snapshot_get_sig(const char *ref,
|
||||
const struct cgit_snapshot_format *f)
|
||||
{
|
||||
struct notes_tree *tree;
|
||||
struct object_id oid;
|
||||
|
||||
if (get_oid(ref, &oid))
|
||||
return NULL;
|
||||
|
||||
tree = &snapshot_sig_notes[f - &cgit_snapshot_formats[0]];
|
||||
if (!tree->initialized) {
|
||||
struct strbuf notes_ref = STRBUF_INIT;
|
||||
|
||||
strbuf_addf(¬es_ref, "refs/notes/signatures/%s",
|
||||
f->suffix + 1);
|
||||
|
||||
init_notes(tree, notes_ref.buf, combine_notes_ignore, 0);
|
||||
strbuf_release(¬es_ref);
|
||||
}
|
||||
|
||||
return get_note(tree, &oid);
|
||||
}
|
||||
|
||||
static const struct cgit_snapshot_format *get_format(const char *filename)
|
||||
{
|
||||
const struct cgit_snapshot_format *fmt;
|
||||
|
||||
for (fmt = cgit_snapshot_formats; fmt->suffix; fmt++) {
|
||||
if (ends_with(filename, fmt->suffix))
|
||||
return fmt;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned cgit_snapshot_format_bit(const struct cgit_snapshot_format *f)
|
||||
{
|
||||
return BIT(f - &cgit_snapshot_formats[0]);
|
||||
}
|
||||
|
||||
static int make_snapshot(const struct cgit_snapshot_format *format,
|
||||
const char *hex, const char *prefix,
|
||||
const char *filename)
|
||||
{
|
||||
struct object_id oid;
|
||||
|
||||
if (get_oid(hex, &oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad object id: %s", hex);
|
||||
return 1;
|
||||
}
|
||||
if (!lookup_commit_reference(the_repository, &oid)) {
|
||||
cgit_print_error_page(400, "Bad request",
|
||||
"Not a commit reference: %s", hex);
|
||||
return 1;
|
||||
}
|
||||
ctx.page.etag = oid_to_hex(&oid);
|
||||
ctx.page.mimetype = xstrdup(format->mimetype);
|
||||
ctx.page.filename = xstrdup(filename);
|
||||
cgit_print_http_headers();
|
||||
init_archivers();
|
||||
format->write_func(hex, prefix);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int write_sig(const struct cgit_snapshot_format *format,
|
||||
const char *hex, const char *archive,
|
||||
const char *filename)
|
||||
{
|
||||
const struct object_id *note = cgit_snapshot_get_sig(hex, format);
|
||||
enum object_type type;
|
||||
unsigned long size;
|
||||
char *buf;
|
||||
|
||||
if (!note) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"No signature for %s", archive);
|
||||
return 0;
|
||||
}
|
||||
|
||||
buf = read_object_file(note, &type, &size);
|
||||
if (!buf) {
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
return 0;
|
||||
}
|
||||
|
||||
html("X-Content-Type-Options: nosniff\n");
|
||||
html("Content-Security-Policy: default-src 'none'\n");
|
||||
ctx.page.etag = oid_to_hex(note);
|
||||
ctx.page.mimetype = xstrdup("application/pgp-signature");
|
||||
ctx.page.filename = xstrdup(filename);
|
||||
cgit_print_http_headers();
|
||||
|
||||
html_raw(buf, size);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Try to guess the requested revision from the requested snapshot name.
|
||||
* First the format extension is stripped, e.g. "cgit-0.7.2.tar.gz" become
|
||||
* "cgit-0.7.2". If this is a valid commit object name we've got a winner.
|
||||
* Otherwise, if the snapshot name has a prefix matching the result from
|
||||
* repo_basename(), we strip the basename and any following '-' and '_'
|
||||
* characters ("cgit-0.7.2" -> "0.7.2") and check the resulting name once
|
||||
* more. If this still isn't a valid commit object name, we check if pre-
|
||||
* pending a 'v' or a 'V' to the remaining snapshot name ("0.7.2" ->
|
||||
* "v0.7.2") gives us something valid.
|
||||
*/
|
||||
static const char *get_ref_from_filename(const struct cgit_repo *repo,
|
||||
const char *filename,
|
||||
const struct cgit_snapshot_format *format)
|
||||
{
|
||||
const char *reponame;
|
||||
struct object_id oid;
|
||||
struct strbuf snapshot = STRBUF_INIT;
|
||||
int result = 1;
|
||||
|
||||
strbuf_addstr(&snapshot, filename);
|
||||
strbuf_setlen(&snapshot, snapshot.len - strlen(format->suffix));
|
||||
|
||||
if (get_oid(snapshot.buf, &oid) == 0)
|
||||
goto out;
|
||||
|
||||
reponame = cgit_snapshot_prefix(repo);
|
||||
if (starts_with(snapshot.buf, reponame)) {
|
||||
const char *new_start = snapshot.buf;
|
||||
new_start += strlen(reponame);
|
||||
while (new_start && (*new_start == '-' || *new_start == '_'))
|
||||
new_start++;
|
||||
strbuf_splice(&snapshot, 0, new_start - snapshot.buf, "", 0);
|
||||
}
|
||||
|
||||
if (get_oid(snapshot.buf, &oid) == 0)
|
||||
goto out;
|
||||
|
||||
strbuf_insert(&snapshot, 0, "v", 1);
|
||||
if (get_oid(snapshot.buf, &oid) == 0)
|
||||
goto out;
|
||||
|
||||
strbuf_splice(&snapshot, 0, 1, "V", 1);
|
||||
if (get_oid(snapshot.buf, &oid) == 0)
|
||||
goto out;
|
||||
|
||||
result = 0;
|
||||
strbuf_release(&snapshot);
|
||||
|
||||
out:
|
||||
return result ? strbuf_detach(&snapshot, NULL) : NULL;
|
||||
}
|
||||
|
||||
void cgit_print_snapshot(const char *head, const char *hex,
|
||||
const char *filename, int dwim)
|
||||
{
|
||||
const struct cgit_snapshot_format* f;
|
||||
const char *sig_filename = NULL;
|
||||
char *adj_filename = NULL;
|
||||
char *prefix = NULL;
|
||||
|
||||
if (!filename) {
|
||||
cgit_print_error_page(400, "Bad request",
|
||||
"No snapshot name specified");
|
||||
return;
|
||||
}
|
||||
|
||||
if (ends_with(filename, ".asc")) {
|
||||
sig_filename = filename;
|
||||
|
||||
/* Strip ".asc" from filename for common format processing */
|
||||
adj_filename = xstrdup(filename);
|
||||
adj_filename[strlen(adj_filename) - 4] = '\0';
|
||||
filename = adj_filename;
|
||||
}
|
||||
|
||||
f = get_format(filename);
|
||||
if (!f || (!sig_filename && !(ctx.repo->snapshots & cgit_snapshot_format_bit(f)))) {
|
||||
cgit_print_error_page(400, "Bad request",
|
||||
"Unsupported snapshot format: %s", filename);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hex && dwim) {
|
||||
hex = get_ref_from_filename(ctx.repo, filename, f);
|
||||
if (hex == NULL) {
|
||||
cgit_print_error_page(404, "Not found", "Not found");
|
||||
return;
|
||||
}
|
||||
prefix = xstrdup(filename);
|
||||
prefix[strlen(filename) - strlen(f->suffix)] = '\0';
|
||||
}
|
||||
|
||||
if (!hex)
|
||||
hex = head;
|
||||
|
||||
if (!prefix)
|
||||
prefix = xstrdup(cgit_snapshot_prefix(ctx.repo));
|
||||
|
||||
if (sig_filename)
|
||||
write_sig(f, hex, filename, sig_filename);
|
||||
else
|
||||
make_snapshot(f, hex, prefix, filename);
|
||||
|
||||
free(prefix);
|
||||
free(adj_filename);
|
||||
}
|
7
ui-snapshot.h
Normal file
7
ui-snapshot.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef UI_SNAPSHOT_H
|
||||
#define UI_SNAPSHOT_H
|
||||
|
||||
extern void cgit_print_snapshot(const char *head, const char *hex,
|
||||
const char *filename, int dwim);
|
||||
|
||||
#endif /* UI_SNAPSHOT_H */
|
420
ui-ssdiff.c
Normal file
420
ui-ssdiff.c
Normal file
|
@ -0,0 +1,420 @@
|
|||
#include "cgit.h"
|
||||
#include "ui-ssdiff.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
#include "ui-diff.h"
|
||||
|
||||
extern int use_ssdiff;
|
||||
|
||||
static int current_old_line, current_new_line;
|
||||
static int **L = NULL;
|
||||
|
||||
struct deferred_lines {
|
||||
int line_no;
|
||||
char *line;
|
||||
struct deferred_lines *next;
|
||||
};
|
||||
|
||||
static struct deferred_lines *deferred_old, *deferred_old_last;
|
||||
static struct deferred_lines *deferred_new, *deferred_new_last;
|
||||
|
||||
static void create_or_reset_lcs_table(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (L != NULL) {
|
||||
memset(*L, 0, sizeof(int) * MAX_SSDIFF_SIZE);
|
||||
return;
|
||||
}
|
||||
|
||||
// xcalloc will die if we ran out of memory;
|
||||
// not very helpful for debugging
|
||||
L = (int**)xcalloc(MAX_SSDIFF_M, sizeof(int *));
|
||||
*L = (int*)xcalloc(MAX_SSDIFF_SIZE, sizeof(int));
|
||||
|
||||
for (i = 1; i < MAX_SSDIFF_M; i++) {
|
||||
L[i] = *L + i * MAX_SSDIFF_N;
|
||||
}
|
||||
}
|
||||
|
||||
static char *longest_common_subsequence(char *A, char *B)
|
||||
{
|
||||
int i, j, ri;
|
||||
int m = strlen(A);
|
||||
int n = strlen(B);
|
||||
int tmp1, tmp2;
|
||||
int lcs_length;
|
||||
char *result;
|
||||
|
||||
// We bail if the lines are too long
|
||||
if (m >= MAX_SSDIFF_M || n >= MAX_SSDIFF_N)
|
||||
return NULL;
|
||||
|
||||
create_or_reset_lcs_table();
|
||||
|
||||
for (i = m; i >= 0; i--) {
|
||||
for (j = n; j >= 0; j--) {
|
||||
if (A[i] == '\0' || B[j] == '\0') {
|
||||
L[i][j] = 0;
|
||||
} else if (A[i] == B[j]) {
|
||||
L[i][j] = 1 + L[i + 1][j + 1];
|
||||
} else {
|
||||
tmp1 = L[i + 1][j];
|
||||
tmp2 = L[i][j + 1];
|
||||
L[i][j] = (tmp1 > tmp2 ? tmp1 : tmp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lcs_length = L[0][0];
|
||||
result = xmalloc(lcs_length + 2);
|
||||
memset(result, 0, sizeof(*result) * (lcs_length + 2));
|
||||
|
||||
ri = 0;
|
||||
i = 0;
|
||||
j = 0;
|
||||
while (i < m && j < n) {
|
||||
if (A[i] == B[j]) {
|
||||
result[ri] = A[i];
|
||||
ri += 1;
|
||||
i += 1;
|
||||
j += 1;
|
||||
} else if (L[i + 1][j] >= L[i][j + 1]) {
|
||||
i += 1;
|
||||
} else {
|
||||
j += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int line_from_hunk(char *line, char type)
|
||||
{
|
||||
char *buf1, *buf2;
|
||||
int len, res;
|
||||
|
||||
buf1 = strchr(line, type);
|
||||
if (buf1 == NULL)
|
||||
return 0;
|
||||
buf1 += 1;
|
||||
buf2 = strchr(buf1, ',');
|
||||
if (buf2 == NULL)
|
||||
return 0;
|
||||
len = buf2 - buf1;
|
||||
buf2 = xmalloc(len + 1);
|
||||
strlcpy(buf2, buf1, len + 1);
|
||||
res = atoi(buf2);
|
||||
free(buf2);
|
||||
return res;
|
||||
}
|
||||
|
||||
static char *replace_tabs(char *line)
|
||||
{
|
||||
char *prev_buf = line;
|
||||
char *cur_buf;
|
||||
size_t linelen = strlen(line);
|
||||
int n_tabs = 0;
|
||||
int i;
|
||||
char *result;
|
||||
size_t result_len;
|
||||
|
||||
if (linelen == 0) {
|
||||
result = xmalloc(1);
|
||||
result[0] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
for (i = 0; i < linelen; i++) {
|
||||
if (line[i] == '\t')
|
||||
n_tabs += 1;
|
||||
}
|
||||
result_len = linelen + n_tabs * 8;
|
||||
result = xmalloc(result_len + 1);
|
||||
result[0] = '\0';
|
||||
|
||||
for (;;) {
|
||||
cur_buf = strchr(prev_buf, '\t');
|
||||
if (!cur_buf) {
|
||||
linelen = strlen(result);
|
||||
strlcpy(&result[linelen], prev_buf, result_len - linelen + 1);
|
||||
break;
|
||||
} else {
|
||||
linelen = strlen(result);
|
||||
strlcpy(&result[linelen], prev_buf, cur_buf - prev_buf + 1);
|
||||
linelen = strlen(result);
|
||||
memset(&result[linelen], ' ', 8 - (linelen % 8));
|
||||
result[linelen + 8 - (linelen % 8)] = '\0';
|
||||
}
|
||||
prev_buf = cur_buf + 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static int calc_deferred_lines(struct deferred_lines *start)
|
||||
{
|
||||
struct deferred_lines *item = start;
|
||||
int result = 0;
|
||||
while (item) {
|
||||
result += 1;
|
||||
item = item->next;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void deferred_old_add(char *line, int line_no)
|
||||
{
|
||||
struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
|
||||
item->line = xstrdup(line);
|
||||
item->line_no = line_no;
|
||||
item->next = NULL;
|
||||
if (deferred_old) {
|
||||
deferred_old_last->next = item;
|
||||
deferred_old_last = item;
|
||||
} else {
|
||||
deferred_old = deferred_old_last = item;
|
||||
}
|
||||
}
|
||||
|
||||
static void deferred_new_add(char *line, int line_no)
|
||||
{
|
||||
struct deferred_lines *item = xmalloc(sizeof(struct deferred_lines));
|
||||
item->line = xstrdup(line);
|
||||
item->line_no = line_no;
|
||||
item->next = NULL;
|
||||
if (deferred_new) {
|
||||
deferred_new_last->next = item;
|
||||
deferred_new_last = item;
|
||||
} else {
|
||||
deferred_new = deferred_new_last = item;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_part_with_lcs(char *class, char *line, char *lcs)
|
||||
{
|
||||
int line_len = strlen(line);
|
||||
int i, j;
|
||||
char c[2] = " ";
|
||||
int same = 1;
|
||||
|
||||
j = 0;
|
||||
for (i = 0; i < line_len; i++) {
|
||||
c[0] = line[i];
|
||||
if (same) {
|
||||
if (line[i] == lcs[j])
|
||||
j += 1;
|
||||
else {
|
||||
same = 0;
|
||||
htmlf("<span class='%s'>", class);
|
||||
}
|
||||
} else if (line[i] == lcs[j]) {
|
||||
same = 1;
|
||||
html("</span>");
|
||||
j += 1;
|
||||
}
|
||||
html_txt(c);
|
||||
}
|
||||
if (!same)
|
||||
html("</span>");
|
||||
}
|
||||
|
||||
static void print_ssdiff_line(char *class,
|
||||
int old_line_no,
|
||||
char *old_line,
|
||||
int new_line_no,
|
||||
char *new_line, int individual_chars)
|
||||
{
|
||||
char *lcs = NULL;
|
||||
|
||||
if (old_line)
|
||||
old_line = replace_tabs(old_line + 1);
|
||||
if (new_line)
|
||||
new_line = replace_tabs(new_line + 1);
|
||||
if (individual_chars && old_line && new_line)
|
||||
lcs = longest_common_subsequence(old_line, new_line);
|
||||
html("<tr>\n");
|
||||
if (old_line_no > 0) {
|
||||
struct diff_filespec *old_file = cgit_get_current_old_file();
|
||||
char *lineno_str = fmt("n%d", old_line_no);
|
||||
char *id_str = fmt("id=%s#%s", is_null_oid(&old_file->oid)?"HEAD":oid_to_hex(old_rev_oid), lineno_str);
|
||||
char *fileurl = cgit_fileurl(ctx.repo->url, "tree", old_file->path, id_str);
|
||||
html("<td class='lineno'><a href='");
|
||||
html(fileurl);
|
||||
htmlf("'>%s</a>", lineno_str + 1);
|
||||
html("</td>");
|
||||
htmlf("<td class='%s'>", class);
|
||||
free(fileurl);
|
||||
} else if (old_line)
|
||||
htmlf("<td class='lineno'></td><td class='%s'>", class);
|
||||
else
|
||||
htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
|
||||
if (old_line) {
|
||||
if (lcs)
|
||||
print_part_with_lcs("del", old_line, lcs);
|
||||
else
|
||||
html_txt(old_line);
|
||||
}
|
||||
|
||||
html("</td>\n");
|
||||
if (new_line_no > 0) {
|
||||
struct diff_filespec *new_file = cgit_get_current_new_file();
|
||||
char *lineno_str = fmt("n%d", new_line_no);
|
||||
char *id_str = fmt("id=%s#%s", is_null_oid(&new_file->oid)?"HEAD":oid_to_hex(new_rev_oid), lineno_str);
|
||||
char *fileurl = cgit_fileurl(ctx.repo->url, "tree", new_file->path, id_str);
|
||||
html("<td class='lineno'><a href='");
|
||||
html(fileurl);
|
||||
htmlf("'>%s</a>", lineno_str + 1);
|
||||
html("</td>");
|
||||
htmlf("<td class='%s'>", class);
|
||||
free(fileurl);
|
||||
} else if (new_line)
|
||||
htmlf("<td class='lineno'></td><td class='%s'>", class);
|
||||
else
|
||||
htmlf("<td class='lineno'></td><td class='%s_dark'>", class);
|
||||
if (new_line) {
|
||||
if (lcs)
|
||||
print_part_with_lcs("add", new_line, lcs);
|
||||
else
|
||||
html_txt(new_line);
|
||||
}
|
||||
|
||||
html("</td></tr>");
|
||||
if (lcs)
|
||||
free(lcs);
|
||||
if (new_line)
|
||||
free(new_line);
|
||||
if (old_line)
|
||||
free(old_line);
|
||||
}
|
||||
|
||||
static void print_deferred_old_lines(void)
|
||||
{
|
||||
struct deferred_lines *iter_old, *tmp;
|
||||
iter_old = deferred_old;
|
||||
while (iter_old) {
|
||||
print_ssdiff_line("del", iter_old->line_no,
|
||||
iter_old->line, -1, NULL, 0);
|
||||
tmp = iter_old->next;
|
||||
free(iter_old);
|
||||
iter_old = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_deferred_new_lines(void)
|
||||
{
|
||||
struct deferred_lines *iter_new, *tmp;
|
||||
iter_new = deferred_new;
|
||||
while (iter_new) {
|
||||
print_ssdiff_line("add", -1, NULL,
|
||||
iter_new->line_no, iter_new->line, 0);
|
||||
tmp = iter_new->next;
|
||||
free(iter_new);
|
||||
iter_new = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
static void print_deferred_changed_lines(void)
|
||||
{
|
||||
struct deferred_lines *iter_old, *iter_new, *tmp;
|
||||
int n_old_lines = calc_deferred_lines(deferred_old);
|
||||
int n_new_lines = calc_deferred_lines(deferred_new);
|
||||
int individual_chars = (n_old_lines == n_new_lines ? 1 : 0);
|
||||
|
||||
iter_old = deferred_old;
|
||||
iter_new = deferred_new;
|
||||
while (iter_old || iter_new) {
|
||||
if (iter_old && iter_new)
|
||||
print_ssdiff_line("changed", iter_old->line_no,
|
||||
iter_old->line,
|
||||
iter_new->line_no, iter_new->line,
|
||||
individual_chars);
|
||||
else if (iter_old)
|
||||
print_ssdiff_line("changed", iter_old->line_no,
|
||||
iter_old->line, -1, NULL, 0);
|
||||
else if (iter_new)
|
||||
print_ssdiff_line("changed", -1, NULL,
|
||||
iter_new->line_no, iter_new->line, 0);
|
||||
if (iter_old) {
|
||||
tmp = iter_old->next;
|
||||
free(iter_old);
|
||||
iter_old = tmp;
|
||||
}
|
||||
|
||||
if (iter_new) {
|
||||
tmp = iter_new->next;
|
||||
free(iter_new);
|
||||
iter_new = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cgit_ssdiff_print_deferred_lines(void)
|
||||
{
|
||||
if (!deferred_old && !deferred_new)
|
||||
return;
|
||||
if (deferred_old && !deferred_new)
|
||||
print_deferred_old_lines();
|
||||
else if (!deferred_old && deferred_new)
|
||||
print_deferred_new_lines();
|
||||
else
|
||||
print_deferred_changed_lines();
|
||||
deferred_old = deferred_old_last = NULL;
|
||||
deferred_new = deferred_new_last = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* print a single line returned from xdiff
|
||||
*/
|
||||
void cgit_ssdiff_line_cb(char *line, int len)
|
||||
{
|
||||
char c = line[len - 1];
|
||||
line[len - 1] = '\0';
|
||||
if (line[0] == '@') {
|
||||
current_old_line = line_from_hunk(line, '-');
|
||||
current_new_line = line_from_hunk(line, '+');
|
||||
}
|
||||
|
||||
if (line[0] == ' ') {
|
||||
if (deferred_old || deferred_new)
|
||||
cgit_ssdiff_print_deferred_lines();
|
||||
print_ssdiff_line("ctx", current_old_line, line,
|
||||
current_new_line, line, 0);
|
||||
current_old_line += 1;
|
||||
current_new_line += 1;
|
||||
} else if (line[0] == '+') {
|
||||
deferred_new_add(line, current_new_line);
|
||||
current_new_line += 1;
|
||||
} else if (line[0] == '-') {
|
||||
deferred_old_add(line, current_old_line);
|
||||
current_old_line += 1;
|
||||
} else if (line[0] == '@') {
|
||||
html("<tr><td colspan='4' class='hunk'>");
|
||||
html_txt(line);
|
||||
html("</td></tr>");
|
||||
} else {
|
||||
html("<tr><td colspan='4' class='ctx'>");
|
||||
html_txt(line);
|
||||
html("</td></tr>");
|
||||
}
|
||||
line[len - 1] = c;
|
||||
}
|
||||
|
||||
void cgit_ssdiff_header_begin(void)
|
||||
{
|
||||
current_old_line = -1;
|
||||
current_new_line = -1;
|
||||
html("<tr><td class='space' colspan='4'><div></div></td></tr>");
|
||||
html("<tr><td class='head' colspan='4'>");
|
||||
}
|
||||
|
||||
void cgit_ssdiff_header_end(void)
|
||||
{
|
||||
html("</td></tr>");
|
||||
}
|
||||
|
||||
void cgit_ssdiff_footer(void)
|
||||
{
|
||||
if (deferred_old || deferred_new)
|
||||
cgit_ssdiff_print_deferred_lines();
|
||||
html("<tr><td class='foot' colspan='4'></td></tr>");
|
||||
}
|
25
ui-ssdiff.h
Normal file
25
ui-ssdiff.h
Normal file
|
@ -0,0 +1,25 @@
|
|||
#ifndef UI_SSDIFF_H
|
||||
#define UI_SSDIFF_H
|
||||
|
||||
/*
|
||||
* ssdiff line limits
|
||||
*/
|
||||
#ifndef MAX_SSDIFF_M
|
||||
#define MAX_SSDIFF_M 128
|
||||
#endif
|
||||
|
||||
#ifndef MAX_SSDIFF_N
|
||||
#define MAX_SSDIFF_N 128
|
||||
#endif
|
||||
#define MAX_SSDIFF_SIZE ((MAX_SSDIFF_M) * (MAX_SSDIFF_N))
|
||||
|
||||
extern void cgit_ssdiff_print_deferred_lines(void);
|
||||
|
||||
extern void cgit_ssdiff_line_cb(char *line, int len);
|
||||
|
||||
extern void cgit_ssdiff_header_begin(void);
|
||||
extern void cgit_ssdiff_header_end(void);
|
||||
|
||||
extern void cgit_ssdiff_footer(void);
|
||||
|
||||
#endif /* UI_SSDIFF_H */
|
426
ui-stats.c
Normal file
426
ui-stats.c
Normal file
|
@ -0,0 +1,426 @@
|
|||
#include "cgit.h"
|
||||
#include "ui-stats.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
struct authorstat {
|
||||
long total;
|
||||
struct string_list list;
|
||||
};
|
||||
|
||||
#define DAY_SECS (60 * 60 * 24)
|
||||
#define WEEK_SECS (DAY_SECS * 7)
|
||||
|
||||
static void trunc_week(struct tm *tm)
|
||||
{
|
||||
time_t t = timegm(tm);
|
||||
t -= ((tm->tm_wday + 6) % 7) * DAY_SECS;
|
||||
gmtime_r(&t, tm);
|
||||
}
|
||||
|
||||
static void dec_week(struct tm *tm)
|
||||
{
|
||||
time_t t = timegm(tm);
|
||||
t -= WEEK_SECS;
|
||||
gmtime_r(&t, tm);
|
||||
}
|
||||
|
||||
static void inc_week(struct tm *tm)
|
||||
{
|
||||
time_t t = timegm(tm);
|
||||
t += WEEK_SECS;
|
||||
gmtime_r(&t, tm);
|
||||
}
|
||||
|
||||
static char *pretty_week(struct tm *tm)
|
||||
{
|
||||
static char buf[10];
|
||||
|
||||
strftime(buf, sizeof(buf), "W%V %G", tm);
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void trunc_month(struct tm *tm)
|
||||
{
|
||||
tm->tm_mday = 1;
|
||||
}
|
||||
|
||||
static void dec_month(struct tm *tm)
|
||||
{
|
||||
tm->tm_mon--;
|
||||
if (tm->tm_mon < 0) {
|
||||
tm->tm_year--;
|
||||
tm->tm_mon = 11;
|
||||
}
|
||||
}
|
||||
|
||||
static void inc_month(struct tm *tm)
|
||||
{
|
||||
tm->tm_mon++;
|
||||
if (tm->tm_mon > 11) {
|
||||
tm->tm_year++;
|
||||
tm->tm_mon = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static char *pretty_month(struct tm *tm)
|
||||
{
|
||||
static const char *months[] = {
|
||||
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
|
||||
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
|
||||
};
|
||||
return fmt("%s %d", months[tm->tm_mon], tm->tm_year + 1900);
|
||||
}
|
||||
|
||||
static void trunc_quarter(struct tm *tm)
|
||||
{
|
||||
trunc_month(tm);
|
||||
while (tm->tm_mon % 3 != 0)
|
||||
dec_month(tm);
|
||||
}
|
||||
|
||||
static void dec_quarter(struct tm *tm)
|
||||
{
|
||||
dec_month(tm);
|
||||
dec_month(tm);
|
||||
dec_month(tm);
|
||||
}
|
||||
|
||||
static void inc_quarter(struct tm *tm)
|
||||
{
|
||||
inc_month(tm);
|
||||
inc_month(tm);
|
||||
inc_month(tm);
|
||||
}
|
||||
|
||||
static char *pretty_quarter(struct tm *tm)
|
||||
{
|
||||
return fmt("Q%d %d", tm->tm_mon / 3 + 1, tm->tm_year + 1900);
|
||||
}
|
||||
|
||||
static void trunc_year(struct tm *tm)
|
||||
{
|
||||
trunc_month(tm);
|
||||
tm->tm_mon = 0;
|
||||
}
|
||||
|
||||
static void dec_year(struct tm *tm)
|
||||
{
|
||||
tm->tm_year--;
|
||||
}
|
||||
|
||||
static void inc_year(struct tm *tm)
|
||||
{
|
||||
tm->tm_year++;
|
||||
}
|
||||
|
||||
static char *pretty_year(struct tm *tm)
|
||||
{
|
||||
return fmt("%d", tm->tm_year + 1900);
|
||||
}
|
||||
|
||||
static const struct cgit_period periods[] = {
|
||||
{'w', "week", 12, 4, trunc_week, dec_week, inc_week, pretty_week},
|
||||
{'m', "month", 12, 4, trunc_month, dec_month, inc_month, pretty_month},
|
||||
{'q', "quarter", 12, 4, trunc_quarter, dec_quarter, inc_quarter, pretty_quarter},
|
||||
{'y', "year", 12, 4, trunc_year, dec_year, inc_year, pretty_year},
|
||||
};
|
||||
|
||||
/* Given a period code or name, return a period index (1, 2, 3 or 4)
|
||||
* and update the period pointer to the correcsponding struct.
|
||||
* If no matching code is found, return 0.
|
||||
*/
|
||||
int cgit_find_stats_period(const char *expr, const struct cgit_period **period)
|
||||
{
|
||||
int i;
|
||||
char code = '\0';
|
||||
|
||||
if (!expr)
|
||||
return 0;
|
||||
|
||||
if (strlen(expr) == 1)
|
||||
code = expr[0];
|
||||
|
||||
for (i = 0; i < sizeof(periods) / sizeof(periods[0]); i++)
|
||||
if (periods[i].code == code || !strcmp(periods[i].name, expr)) {
|
||||
if (period)
|
||||
*period = &periods[i];
|
||||
return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cgit_find_stats_periodname(int idx)
|
||||
{
|
||||
if (idx > 0 && idx < 4)
|
||||
return periods[idx - 1].name;
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
static void add_commit(struct string_list *authors, struct commit *commit,
|
||||
const struct cgit_period *period)
|
||||
{
|
||||
struct commitinfo *info;
|
||||
struct string_list_item *author, *item;
|
||||
struct authorstat *authorstat;
|
||||
struct string_list *items;
|
||||
char *tmp;
|
||||
struct tm *date;
|
||||
time_t t;
|
||||
uintptr_t *counter;
|
||||
|
||||
info = cgit_parse_commit(commit);
|
||||
tmp = xstrdup(info->author);
|
||||
author = string_list_insert(authors, tmp);
|
||||
if (!author->util)
|
||||
author->util = xcalloc(1, sizeof(struct authorstat));
|
||||
else
|
||||
free(tmp);
|
||||
authorstat = author->util;
|
||||
items = &authorstat->list;
|
||||
t = info->committer_date;
|
||||
date = gmtime(&t);
|
||||
period->trunc(date);
|
||||
tmp = xstrdup(period->pretty(date));
|
||||
item = string_list_insert(items, tmp);
|
||||
counter = (uintptr_t *)&item->util;
|
||||
if (*counter)
|
||||
free(tmp);
|
||||
(*counter)++;
|
||||
|
||||
authorstat->total++;
|
||||
cgit_free_commitinfo(info);
|
||||
}
|
||||
|
||||
static int cmp_total_commits(const void *a1, const void *a2)
|
||||
{
|
||||
const struct string_list_item *i1 = a1;
|
||||
const struct string_list_item *i2 = a2;
|
||||
const struct authorstat *auth1 = i1->util;
|
||||
const struct authorstat *auth2 = i2->util;
|
||||
|
||||
return auth2->total - auth1->total;
|
||||
}
|
||||
|
||||
/* Walk the commit DAG and collect number of commits per author per
|
||||
* timeperiod into a nested string_list collection.
|
||||
*/
|
||||
static struct string_list collect_stats(const struct cgit_period *period)
|
||||
{
|
||||
struct string_list authors;
|
||||
struct rev_info rev;
|
||||
struct commit *commit;
|
||||
const char *argv[] = {NULL, ctx.qry.head, NULL, NULL, NULL, NULL};
|
||||
int argc = 3;
|
||||
time_t now;
|
||||
long i;
|
||||
struct tm *tm;
|
||||
char tmp[11];
|
||||
|
||||
time(&now);
|
||||
tm = gmtime(&now);
|
||||
period->trunc(tm);
|
||||
for (i = 1; i < period->count; i++)
|
||||
period->dec(tm);
|
||||
strftime(tmp, sizeof(tmp), "%Y-%m-%d", tm);
|
||||
argv[2] = xstrdup(fmt("--since=%s", tmp));
|
||||
if (ctx.qry.path) {
|
||||
argv[3] = "--";
|
||||
argv[4] = ctx.qry.path;
|
||||
argc += 2;
|
||||
}
|
||||
init_revisions(&rev, NULL);
|
||||
rev.abbrev = DEFAULT_ABBREV;
|
||||
rev.commit_format = CMIT_FMT_DEFAULT;
|
||||
rev.max_parents = 1;
|
||||
rev.verbose_header = 1;
|
||||
rev.show_root_diff = 0;
|
||||
setup_revisions(argc, argv, &rev, NULL);
|
||||
prepare_revision_walk(&rev);
|
||||
memset(&authors, 0, sizeof(authors));
|
||||
while ((commit = get_revision(&rev)) != NULL) {
|
||||
add_commit(&authors, commit, period);
|
||||
free_commit_buffer(the_repository->parsed_objects, commit);
|
||||
free_commit_list(commit->parents);
|
||||
commit->parents = NULL;
|
||||
}
|
||||
return authors;
|
||||
}
|
||||
|
||||
static void print_combined_authorrow(struct string_list *authors, int from,
|
||||
int to, const char *name,
|
||||
const char *leftclass,
|
||||
const char *centerclass,
|
||||
const char *rightclass,
|
||||
const struct cgit_period *period)
|
||||
{
|
||||
struct string_list_item *author;
|
||||
struct authorstat *authorstat;
|
||||
struct string_list *items;
|
||||
struct string_list_item *date;
|
||||
time_t now;
|
||||
long i, j, total, subtotal;
|
||||
struct tm *tm;
|
||||
char *tmp;
|
||||
|
||||
time(&now);
|
||||
tm = gmtime(&now);
|
||||
period->trunc(tm);
|
||||
for (i = 1; i < period->count; i++)
|
||||
period->dec(tm);
|
||||
|
||||
total = 0;
|
||||
htmlf("<tr><td class='%s'>%s</td>", leftclass,
|
||||
fmt(name, to - from + 1));
|
||||
for (j = 0; j < period->count; j++) {
|
||||
tmp = period->pretty(tm);
|
||||
period->inc(tm);
|
||||
subtotal = 0;
|
||||
for (i = from; i <= to; i++) {
|
||||
author = &authors->items[i];
|
||||
authorstat = author->util;
|
||||
items = &authorstat->list;
|
||||
date = string_list_lookup(items, tmp);
|
||||
if (date)
|
||||
subtotal += (uintptr_t)date->util;
|
||||
}
|
||||
htmlf("<td class='%s'>%ld</td>", centerclass, subtotal);
|
||||
total += subtotal;
|
||||
}
|
||||
htmlf("<td class='%s'>%ld</td></tr>", rightclass, total);
|
||||
}
|
||||
|
||||
static void print_authors(struct string_list *authors, int top,
|
||||
const struct cgit_period *period)
|
||||
{
|
||||
struct string_list_item *author;
|
||||
struct authorstat *authorstat;
|
||||
struct string_list *items;
|
||||
struct string_list_item *date;
|
||||
time_t now;
|
||||
long i, j, total;
|
||||
struct tm *tm;
|
||||
char *tmp;
|
||||
|
||||
time(&now);
|
||||
tm = gmtime(&now);
|
||||
period->trunc(tm);
|
||||
for (i = 1; i < period->count; i++)
|
||||
period->dec(tm);
|
||||
|
||||
html("<table class='stats'><tr><th>Author</th>");
|
||||
for (j = 0; j < period->count; j++) {
|
||||
tmp = period->pretty(tm);
|
||||
htmlf("<th>%s</th>", tmp);
|
||||
period->inc(tm);
|
||||
}
|
||||
html("<th>Total</th></tr>\n");
|
||||
|
||||
if (top <= 0 || top > authors->nr)
|
||||
top = authors->nr;
|
||||
|
||||
for (i = 0; i < top; i++) {
|
||||
author = &authors->items[i];
|
||||
html("<tr><td class='left'>");
|
||||
html_txt(author->string);
|
||||
html("</td>");
|
||||
authorstat = author->util;
|
||||
items = &authorstat->list;
|
||||
total = 0;
|
||||
for (j = 0; j < period->count; j++)
|
||||
period->dec(tm);
|
||||
for (j = 0; j < period->count; j++) {
|
||||
tmp = period->pretty(tm);
|
||||
period->inc(tm);
|
||||
date = string_list_lookup(items, tmp);
|
||||
if (!date)
|
||||
html("<td>0</td>");
|
||||
else {
|
||||
htmlf("<td>%lu</td>", (uintptr_t)date->util);
|
||||
total += (uintptr_t)date->util;
|
||||
}
|
||||
}
|
||||
htmlf("<td class='sum'>%ld</td></tr>", total);
|
||||
}
|
||||
|
||||
if (top < authors->nr)
|
||||
print_combined_authorrow(authors, top, authors->nr - 1,
|
||||
"Others (%ld)", "left", "", "sum", period);
|
||||
|
||||
print_combined_authorrow(authors, 0, authors->nr - 1, "Total",
|
||||
"total", "sum", "sum", period);
|
||||
html("</table>");
|
||||
}
|
||||
|
||||
/* Create a sorted string_list with one entry per author. The util-field
|
||||
* for each author is another string_list which is used to calculate the
|
||||
* number of commits per time-interval.
|
||||
*/
|
||||
void cgit_show_stats(void)
|
||||
{
|
||||
struct string_list authors;
|
||||
const struct cgit_period *period;
|
||||
int top, i;
|
||||
const char *code = "w";
|
||||
|
||||
if (ctx.qry.period)
|
||||
code = ctx.qry.period;
|
||||
|
||||
i = cgit_find_stats_period(code, &period);
|
||||
if (!i) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Unknown statistics type: %c", code[0]);
|
||||
return;
|
||||
}
|
||||
if (i > ctx.repo->max_stats) {
|
||||
cgit_print_error_page(400, "Bad request",
|
||||
"Statistics type disabled: %s", period->name);
|
||||
return;
|
||||
}
|
||||
authors = collect_stats(period);
|
||||
qsort(authors.items, authors.nr, sizeof(struct string_list_item),
|
||||
cmp_total_commits);
|
||||
|
||||
top = ctx.qry.ofs;
|
||||
if (!top)
|
||||
top = 10;
|
||||
|
||||
cgit_print_layout_start();
|
||||
html("<div class='cgit-panel'>");
|
||||
html("<b>stat options</b>");
|
||||
html("<form method='get'>");
|
||||
cgit_add_hidden_formfields(1, 0, "stats");
|
||||
html("<table><tr><td colspan='2'/></tr>");
|
||||
if (ctx.repo->max_stats > 1) {
|
||||
html("<tr><td class='label'>Period:</td>");
|
||||
html("<td class='ctrl'><select name='period' onchange='this.form.submit();'>");
|
||||
for (i = 0; i < ctx.repo->max_stats; i++)
|
||||
html_option(fmt("%c", periods[i].code),
|
||||
periods[i].name, fmt("%c", period->code));
|
||||
html("</select></td></tr>");
|
||||
}
|
||||
html("<tr><td class='label'>Authors:</td>");
|
||||
html("<td class='ctrl'><select name='ofs' onchange='this.form.submit();'>");
|
||||
html_intoption(10, "10", top);
|
||||
html_intoption(25, "25", top);
|
||||
html_intoption(50, "50", top);
|
||||
html_intoption(100, "100", top);
|
||||
html_intoption(-1, "all", top);
|
||||
html("</select></td></tr>");
|
||||
html("<tr><td/><td class='ctrl'>");
|
||||
html("<noscript><input type='submit' value='Reload'/></noscript>");
|
||||
html("</td></tr></table>");
|
||||
html("</form>");
|
||||
html("</div>");
|
||||
htmlf("<h2>Commits per author per %s", period->name);
|
||||
if (ctx.qry.path) {
|
||||
html(" (path '");
|
||||
html_txt(ctx.qry.path);
|
||||
html("')");
|
||||
}
|
||||
html("</h2>");
|
||||
print_authors(&authors, top, period);
|
||||
cgit_print_layout_end();
|
||||
}
|
||||
|
28
ui-stats.h
Normal file
28
ui-stats.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#ifndef UI_STATS_H
|
||||
#define UI_STATS_H
|
||||
|
||||
#include "cgit.h"
|
||||
|
||||
struct cgit_period {
|
||||
const char code;
|
||||
const char *name;
|
||||
int max_periods;
|
||||
int count;
|
||||
|
||||
/* Convert a tm value to the first day in the period */
|
||||
void (*trunc)(struct tm *tm);
|
||||
|
||||
/* Update tm value to start of next/previous period */
|
||||
void (*dec)(struct tm *tm);
|
||||
void (*inc)(struct tm *tm);
|
||||
|
||||
/* Pretty-print a tm value */
|
||||
char *(*pretty)(struct tm *tm);
|
||||
};
|
||||
|
||||
extern int cgit_find_stats_period(const char *expr, const struct cgit_period **period);
|
||||
extern const char *cgit_find_stats_periodname(int idx);
|
||||
|
||||
extern void cgit_show_stats(void);
|
||||
|
||||
#endif /* UI_STATS_H */
|
148
ui-summary.c
Normal file
148
ui-summary.c
Normal file
|
@ -0,0 +1,148 @@
|
|||
/* ui-summary.c: functions for generating repo summary page
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-summary.h"
|
||||
#include "html.h"
|
||||
#include "ui-blob.h"
|
||||
#include "ui-log.h"
|
||||
#include "ui-plain.h"
|
||||
#include "ui-refs.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
static int urls;
|
||||
|
||||
static void print_url(const char *url)
|
||||
{
|
||||
int columns = 3;
|
||||
|
||||
if (ctx.repo->enable_log_filecount)
|
||||
columns++;
|
||||
if (ctx.repo->enable_log_linecount)
|
||||
columns++;
|
||||
|
||||
if (urls++ == 0) {
|
||||
htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns);
|
||||
htmlf("<tr class='nohover'><th class='left' colspan='%d'>Clone</th></tr>\n", columns);
|
||||
}
|
||||
|
||||
htmlf("<tr><td colspan='%d'><a rel='vcs-git' href='", columns);
|
||||
html_url_path(url);
|
||||
html("' title='");
|
||||
html_attr(ctx.repo->name);
|
||||
html(" Git repository'>");
|
||||
html_txt(url);
|
||||
html("</a></td></tr>\n");
|
||||
}
|
||||
|
||||
void cgit_print_summary(void)
|
||||
{
|
||||
int columns = 3;
|
||||
|
||||
if (ctx.repo->enable_log_filecount)
|
||||
columns++;
|
||||
if (ctx.repo->enable_log_linecount)
|
||||
columns++;
|
||||
|
||||
cgit_print_layout_start();
|
||||
html("<table summary='repository info' class='list nowrap'>");
|
||||
cgit_print_branches(ctx.cfg.summary_branches);
|
||||
htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns);
|
||||
cgit_print_tags(ctx.cfg.summary_tags);
|
||||
if (ctx.cfg.summary_log > 0) {
|
||||
htmlf("<tr class='nohover'><td colspan='%d'> </td></tr>", columns);
|
||||
cgit_print_log(ctx.qry.head, 0, ctx.cfg.summary_log, NULL,
|
||||
NULL, NULL, 0, 0, 0);
|
||||
}
|
||||
urls = 0;
|
||||
cgit_add_clone_urls(print_url);
|
||||
html("</table>");
|
||||
cgit_print_layout_end();
|
||||
}
|
||||
|
||||
/* The caller must free the return value. */
|
||||
static char* append_readme_path(const char *filename, const char *ref, const char *path)
|
||||
{
|
||||
char *file, *base_dir, *full_path, *resolved_base = NULL, *resolved_full = NULL;
|
||||
/* If a subpath is specified for the about page, make it relative
|
||||
* to the directory containing the configured readme. */
|
||||
|
||||
file = xstrdup(filename);
|
||||
base_dir = dirname(file);
|
||||
if (!strcmp(base_dir, ".") || !strcmp(base_dir, "..")) {
|
||||
if (!ref) {
|
||||
free(file);
|
||||
return NULL;
|
||||
}
|
||||
full_path = xstrdup(path);
|
||||
} else
|
||||
full_path = fmtalloc("%s/%s", base_dir, path);
|
||||
|
||||
if (!ref) {
|
||||
resolved_base = realpath(base_dir, NULL);
|
||||
resolved_full = realpath(full_path, NULL);
|
||||
if (!resolved_base || !resolved_full || !starts_with(resolved_full, resolved_base)) {
|
||||
free(full_path);
|
||||
full_path = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
free(file);
|
||||
free(resolved_base);
|
||||
free(resolved_full);
|
||||
|
||||
return full_path;
|
||||
}
|
||||
|
||||
void cgit_print_repo_readme(const char *path)
|
||||
{
|
||||
char *filename, *ref, *mimetype;
|
||||
int free_filename = 0;
|
||||
|
||||
mimetype = get_mimetype_for_filename(path);
|
||||
if (mimetype && (!strncmp(mimetype, "image/", 6) || !strncmp(mimetype, "video/", 6))) {
|
||||
ctx.page.mimetype = mimetype;
|
||||
ctx.page.charset = NULL;
|
||||
cgit_print_plain();
|
||||
free(mimetype);
|
||||
return;
|
||||
}
|
||||
free(mimetype);
|
||||
|
||||
cgit_print_layout_start();
|
||||
if (ctx.repo->readme.nr == 0)
|
||||
goto done;
|
||||
|
||||
filename = ctx.repo->readme.items[0].string;
|
||||
ref = ctx.repo->readme.items[0].util;
|
||||
|
||||
if (path) {
|
||||
free_filename = 1;
|
||||
filename = append_readme_path(filename, ref, path);
|
||||
if (!filename)
|
||||
goto done;
|
||||
}
|
||||
|
||||
/* Print the calculated readme, either from the git repo or from the
|
||||
* filesystem, while applying the about-filter.
|
||||
*/
|
||||
html("<div id='summary'>");
|
||||
cgit_open_filter(ctx.repo->about_filter, filename);
|
||||
if (ref)
|
||||
cgit_print_file(filename, ref, 1);
|
||||
else
|
||||
html_include(filename);
|
||||
cgit_close_filter(ctx.repo->about_filter);
|
||||
|
||||
html("</div>");
|
||||
if (free_filename)
|
||||
free(filename);
|
||||
|
||||
done:
|
||||
cgit_print_layout_end();
|
||||
}
|
7
ui-summary.h
Normal file
7
ui-summary.h
Normal file
|
@ -0,0 +1,7 @@
|
|||
#ifndef UI_SUMMARY_H
|
||||
#define UI_SUMMARY_H
|
||||
|
||||
extern void cgit_print_summary(void);
|
||||
extern void cgit_print_repo_readme(const char *path);
|
||||
|
||||
#endif /* UI_SUMMARY_H */
|
120
ui-tag.c
Normal file
120
ui-tag.c
Normal file
|
@ -0,0 +1,120 @@
|
|||
/* ui-tag.c: display a tag
|
||||
*
|
||||
* Copyright (C) 2006-2014 cgit Development Team <cgit@lists.zx2c4.com>
|
||||
*
|
||||
* Licensed under GNU General Public License v2
|
||||
* (see COPYING for full license text)
|
||||
*/
|
||||
|
||||
#include "cgit.h"
|
||||
#include "ui-tag.h"
|
||||
#include "html.h"
|
||||
#include "ui-shared.h"
|
||||
|
||||
static void print_tag_content(char *buf)
|
||||
{
|
||||
char *p;
|
||||
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
html("<div class='commit-subject'>");
|
||||
p = strchr(buf, '\n');
|
||||
if (p)
|
||||
*p = '\0';
|
||||
html_txt(buf);
|
||||
html("</div>");
|
||||
if (p) {
|
||||
html("<div class='commit-msg'>");
|
||||
html_txt(++p);
|
||||
html("</div>");
|
||||
}
|
||||
}
|
||||
|
||||
static void print_download_links(char *revname)
|
||||
{
|
||||
html("<tr><th>download</th><td class='sha1'>");
|
||||
cgit_print_snapshot_links(ctx.repo, revname, "<br/>");
|
||||
html("</td></tr>");
|
||||
}
|
||||
|
||||
void cgit_print_tag(char *revname)
|
||||
{
|
||||
struct strbuf fullref = STRBUF_INIT;
|
||||
struct object_id oid;
|
||||
struct object *obj;
|
||||
|
||||
if (!revname)
|
||||
revname = ctx.qry.head;
|
||||
|
||||
strbuf_addf(&fullref, "refs/tags/%s", revname);
|
||||
if (get_oid(fullref.buf, &oid)) {
|
||||
cgit_print_error_page(404, "Not found",
|
||||
"Bad tag reference: %s", revname);
|
||||
goto cleanup;
|
||||
}
|
||||
obj = parse_object(the_repository, &oid);
|
||||
if (!obj) {
|
||||
cgit_print_error_page(500, "Internal server error",
|
||||
"Bad object id: %s", oid_to_hex(&oid));
|
||||
goto cleanup;
|
||||
}
|
||||
if (obj->type == OBJ_TAG) {
|
||||
struct tag *tag;
|
||||
struct taginfo *info;
|
||||
|
||||
tag = lookup_tag(the_repository, &oid);
|
||||
if (!tag || parse_tag(tag) || !(info = cgit_parse_tag(tag))) {
|
||||
cgit_print_error_page(500, "Internal server error",
|
||||
"Bad tag object: %s", revname);
|
||||
goto cleanup;
|
||||
}
|
||||
cgit_print_layout_start();
|
||||
html("<table class='commit-info'>\n");
|
||||
html("<tr><td>tag name</td><td>");
|
||||
html_txt(revname);
|
||||
htmlf(" (%s)</td></tr>\n", oid_to_hex(&oid));
|
||||
if (info->tagger_date > 0) {
|
||||
html("<tr><td>tag date</td><td>");
|
||||
html_txt(show_date(info->tagger_date, info->tagger_tz,
|
||||
cgit_date_mode(DATE_ISO8601)));
|
||||
html("</td></tr>\n");
|
||||
}
|
||||
if (info->tagger) {
|
||||
html("<tr><td>tagged by</td><td>");
|
||||
cgit_open_filter(ctx.repo->email_filter, info->tagger_email, "tag");
|
||||
html_txt(info->tagger);
|
||||
if (info->tagger_email && !ctx.cfg.noplainemail) {
|
||||
html(" ");
|
||||
html_txt(info->tagger_email);
|
||||
}
|
||||
cgit_close_filter(ctx.repo->email_filter);
|
||||
html("</td></tr>\n");
|
||||
}
|
||||
html("<tr><td>tagged object</td><td class='sha1'>");
|
||||
cgit_object_link(tag->tagged);
|
||||
html("</td></tr>\n");
|
||||
if (ctx.repo->snapshots)
|
||||
print_download_links(revname);
|
||||
html("</table>\n");
|
||||
print_tag_content(info->msg);
|
||||
cgit_print_layout_end();
|
||||
cgit_free_taginfo(info);
|
||||
} else {
|
||||
cgit_print_layout_start();
|
||||
html("<table class='commit-info'>\n");
|
||||
html("<tr><td>tag name</td><td>");
|
||||
html_txt(revname);
|
||||
html("</td></tr>\n");
|
||||
html("<tr><td>tagged object</td><td class='sha1'>");
|
||||
cgit_object_link(obj);
|
||||
html("</td></tr>\n");
|
||||
if (ctx.repo->snapshots)
|
||||
print_download_links(revname);
|
||||
html("</table>\n");
|
||||
cgit_print_layout_end();
|
||||
}
|
||||
|
||||
cleanup:
|
||||
strbuf_release(&fullref);
|
||||
}
|
6
ui-tag.h
Normal file
6
ui-tag.h
Normal file
|
@ -0,0 +1,6 @@
|
|||
#ifndef UI_TAG_H
|
||||
#define UI_TAG_H
|
||||
|
||||
extern void cgit_print_tag(char *revname);
|
||||
|
||||
#endif /* UI_TAG_H */
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue