pax_global_header00006660000000000000000000000064130152150040014501gustar00rootroot0000000000000052 comment=8e9ddd21a50beb9fd660cf6cd6a583234924b932 cgit-1.1/000077500000000000000000000000001301521500400122705ustar00rootroot00000000000000cgit-1.1/.gitignore000066400000000000000000000002321301521500400142550ustar00rootroot00000000000000# 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 cgit-1.1/.gitmodules000066400000000000000000000001161301521500400144430ustar00rootroot00000000000000[submodule "git"] url = git://git.kernel.org/pub/scm/git/git.git path = git cgit-1.1/.mailmap000066400000000000000000000011031301521500400137040ustar00rootroot00000000000000Florian Pritz Harley Laue John Keeping Lars Hjemli Lars Hjemli Lars Hjemli Lars Hjemli Lukas Fleischer Lukas Fleischer Stefan Bühler cgit-1.1/AUTHORS000066400000000000000000000005241301521500400133410ustar00rootroot00000000000000Maintainer: Jason A. Donenfeld Contributors: Jason A. Donenfeld Lukas Fleischer Johan Herland Lars Hjemli Ferry Huberts John Keeping Previous Maintainer: Lars Hjemli cgit-1.1/COPYING000066400000000000000000000431311301521500400133250ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. cgit-1.1/Makefile000066400000000000000000000105271301521500400137350ustar00rootroot00000000000000all:: CGIT_VERSION = v1.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 = GIT_VER = 2.10.2 GIT_URL = https://www.kernel.org/pub/software/scm/git/git-$(GIT_VER).tar.gz 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)) # 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 a2x -f xhtml --stylesheet=cgit-doc.css $< $(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 -xzf - && 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 cgit-1.1/README000066400000000000000000000056741301521500400131640ustar00rootroot00000000000000cgit - 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: AllowOverride None Options +ExecCGI Order allow,deny Allow from all 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 * Patches, bug reports, discussions and support should go to the cgit mailing list: . To sign up, visit cgit-1.1/cache.c000066400000000000000000000250751301521500400135100ustar00rootroot00000000000000/* cache.c: cache management * * Copyright (C) 2006-2014 cgit Development Team * * 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 #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; 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); 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) { int tmp; /* Preserve stdout */ tmp = dup(STDOUT_FILENO); if (tmp == -1) return errno; /* Redirect stdout to lockfile */ if (dup2(slot->lock_fd, STDOUT_FILENO) == -1) { close(tmp); return errno; } /* Generate cache content */ slot->fn(); /* update stat info */ if (fstat(slot->lock_fd, &slot->cache_st)) { close(tmp); return errno; } /* Restore stdout */ if (dup2(tmp, STDOUT_FILENO) == -1) { close(tmp); return errno; } /* Close the temporary filedescriptor */ if (close(tmp)) 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 belive * 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.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); } cgit-1.1/cache.h000066400000000000000000000017251301521500400135110ustar00rootroot00000000000000/* * 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, everyting 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 */ cgit-1.1/cgit-doc.css000066400000000000000000000000521301521500400144700ustar00rootroot00000000000000div.variablelist dt { margin-top: 1em; } cgit-1.1/cgit.c000066400000000000000000001040011301521500400133560ustar00rootroot00000000000000/* cgit.c: cgi for the git scm * * Copyright (C) 2006-2014 cgit Development Team * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "cache.h" #include "cmd.h" #include "configfile.h" #include "html.h" #include "ui-shared.h" #include "ui-stats.h" #include "ui-blob.h" #include "ui-summary.h" #include "scan-tree.h" const char *cgit_version = CGIT_VERSION; static void add_mimetype(const char *name, const char *value) { struct string_list_item *item; item = string_list_insert(&ctx.cfg.mimetypes, xstrdup(name)); item->util = xstrdup(value); } static void process_cached_repolist(const char *path); static void repo_config(struct cgit_repo *repo, const char *name, const char *value) { const char *path; struct string_list_item *item; if (!strcmp(name, "name")) repo->name = xstrdup(value); else if (!strcmp(name, "clone-url")) repo->clone_url = xstrdup(value); else if (!strcmp(name, "desc")) repo->desc = xstrdup(value); else if (!strcmp(name, "owner")) repo->owner = xstrdup(value); else if (!strcmp(name, "homepage")) repo->homepage = xstrdup(value); else if (!strcmp(name, "defbranch")) repo->defbranch = xstrdup(value); else if (!strcmp(name, "snapshots")) repo->snapshots = ctx.cfg.snapshots & cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-commit-graph")) repo->enable_commit_graph = atoi(value); else if (!strcmp(name, "enable-log-filecount")) repo->enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) repo->enable_log_linecount = atoi(value); else if (!strcmp(name, "enable-remote-branches")) repo->enable_remote_branches = atoi(value); else if (!strcmp(name, "enable-subject-links")) repo->enable_subject_links = atoi(value); else if (!strcmp(name, "enable-html-serving")) repo->enable_html_serving = atoi(value); else if (!strcmp(name, "branch-sort")) { if (!strcmp(value, "age")) repo->branch_sort = 1; if (!strcmp(value, "name")) repo->branch_sort = 0; } else if (!strcmp(name, "commit-sort")) { if (!strcmp(value, "date")) repo->commit_sort = 1; if (!strcmp(value, "topo")) repo->commit_sort = 2; } else if (!strcmp(name, "max-stats")) repo->max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "module-link")) repo->module_link= xstrdup(value); else if (skip_prefix(name, "module-link.", &path)) { item = string_list_append(&repo->submodules, xstrdup(path)); item->util = xstrdup(value); } else if (!strcmp(name, "section")) repo->section = xstrdup(value); else if (!strcmp(name, "readme") && value != NULL) { if (repo->readme.items == ctx.cfg.readme.items) memset(&repo->readme, 0, sizeof(repo->readme)); string_list_append(&repo->readme, xstrdup(value)); } else if (!strcmp(name, "logo") && value != NULL) repo->logo = xstrdup(value); else if (!strcmp(name, "logo-link") && value != NULL) repo->logo_link = xstrdup(value); else if (!strcmp(name, "hide")) repo->hide = atoi(value); else if (!strcmp(name, "ignore")) repo->ignore = atoi(value); else if (ctx.cfg.enable_filter_overrides) { if (!strcmp(name, "about-filter")) repo->about_filter = cgit_new_filter(value, ABOUT); else if (!strcmp(name, "commit-filter")) repo->commit_filter = cgit_new_filter(value, COMMIT); else if (!strcmp(name, "source-filter")) repo->source_filter = cgit_new_filter(value, SOURCE); else if (!strcmp(name, "email-filter")) repo->email_filter = cgit_new_filter(value, EMAIL); else if (!strcmp(name, "owner-filter")) repo->owner_filter = cgit_new_filter(value, OWNER); } } static void config_cb(const char *name, const char *value) { const char *arg; if (!strcmp(name, "section") || !strcmp(name, "repo.group")) ctx.cfg.section = xstrdup(value); else if (!strcmp(name, "repo.url")) ctx.repo = cgit_add_repo(value); else if (ctx.repo && !strcmp(name, "repo.path")) ctx.repo->path = trim_end(value, '/'); else if (ctx.repo && skip_prefix(name, "repo.", &arg)) repo_config(ctx.repo, arg, value); else if (!strcmp(name, "readme")) string_list_append(&ctx.cfg.readme, xstrdup(value)); else if (!strcmp(name, "root-title")) ctx.cfg.root_title = xstrdup(value); else if (!strcmp(name, "root-desc")) ctx.cfg.root_desc = xstrdup(value); else if (!strcmp(name, "root-readme")) ctx.cfg.root_readme = xstrdup(value); else if (!strcmp(name, "css")) ctx.cfg.css = xstrdup(value); else if (!strcmp(name, "favicon")) ctx.cfg.favicon = xstrdup(value); else if (!strcmp(name, "footer")) ctx.cfg.footer = xstrdup(value); else if (!strcmp(name, "head-include")) ctx.cfg.head_include = xstrdup(value); else if (!strcmp(name, "header")) ctx.cfg.header = xstrdup(value); else if (!strcmp(name, "logo")) ctx.cfg.logo = xstrdup(value); else if (!strcmp(name, "index-header")) ctx.cfg.index_header = xstrdup(value); else if (!strcmp(name, "index-info")) ctx.cfg.index_info = xstrdup(value); else if (!strcmp(name, "logo-link")) ctx.cfg.logo_link = xstrdup(value); else if (!strcmp(name, "module-link")) ctx.cfg.module_link = xstrdup(value); else if (!strcmp(name, "strict-export")) ctx.cfg.strict_export = xstrdup(value); else if (!strcmp(name, "virtual-root")) { ctx.cfg.virtual_root = ensure_end(value, '/'); } else if (!strcmp(name, "nocache")) ctx.cfg.nocache = atoi(value); else if (!strcmp(name, "noplainemail")) ctx.cfg.noplainemail = atoi(value); else if (!strcmp(name, "noheader")) ctx.cfg.noheader = atoi(value); else if (!strcmp(name, "snapshots")) ctx.cfg.snapshots = cgit_parse_snapshots_mask(value); else if (!strcmp(name, "enable-filter-overrides")) ctx.cfg.enable_filter_overrides = atoi(value); else if (!strcmp(name, "enable-follow-links")) ctx.cfg.enable_follow_links = atoi(value); else if (!strcmp(name, "enable-http-clone")) ctx.cfg.enable_http_clone = atoi(value); else if (!strcmp(name, "enable-index-links")) ctx.cfg.enable_index_links = atoi(value); else if (!strcmp(name, "enable-index-owner")) ctx.cfg.enable_index_owner = atoi(value); else if (!strcmp(name, "enable-commit-graph")) ctx.cfg.enable_commit_graph = atoi(value); else if (!strcmp(name, "enable-log-filecount")) ctx.cfg.enable_log_filecount = atoi(value); else if (!strcmp(name, "enable-log-linecount")) ctx.cfg.enable_log_linecount = atoi(value); else if (!strcmp(name, "enable-remote-branches")) ctx.cfg.enable_remote_branches = atoi(value); else if (!strcmp(name, "enable-subject-links")) ctx.cfg.enable_subject_links = atoi(value); else if (!strcmp(name, "enable-html-serving")) ctx.cfg.enable_html_serving = atoi(value); else if (!strcmp(name, "enable-tree-linenumbers")) ctx.cfg.enable_tree_linenumbers = atoi(value); else if (!strcmp(name, "enable-git-config")) ctx.cfg.enable_git_config = atoi(value); else if (!strcmp(name, "max-stats")) ctx.cfg.max_stats = cgit_find_stats_period(value, NULL); else if (!strcmp(name, "cache-size")) ctx.cfg.cache_size = atoi(value); else if (!strcmp(name, "cache-root")) ctx.cfg.cache_root = xstrdup(expand_macros(value)); else if (!strcmp(name, "cache-root-ttl")) ctx.cfg.cache_root_ttl = atoi(value); else if (!strcmp(name, "cache-repo-ttl")) ctx.cfg.cache_repo_ttl = atoi(value); else if (!strcmp(name, "cache-scanrc-ttl")) ctx.cfg.cache_scanrc_ttl = atoi(value); else if (!strcmp(name, "cache-static-ttl")) ctx.cfg.cache_static_ttl = atoi(value); else if (!strcmp(name, "cache-dynamic-ttl")) ctx.cfg.cache_dynamic_ttl = atoi(value); else if (!strcmp(name, "cache-about-ttl")) ctx.cfg.cache_about_ttl = atoi(value); else if (!strcmp(name, "cache-snapshot-ttl")) ctx.cfg.cache_snapshot_ttl = atoi(value); else if (!strcmp(name, "case-sensitive-sort")) ctx.cfg.case_sensitive_sort = atoi(value); else if (!strcmp(name, "about-filter")) ctx.cfg.about_filter = cgit_new_filter(value, ABOUT); else if (!strcmp(name, "commit-filter")) ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT); else if (!strcmp(name, "email-filter")) ctx.cfg.email_filter = cgit_new_filter(value, EMAIL); else if (!strcmp(name, "owner-filter")) ctx.cfg.owner_filter = cgit_new_filter(value, OWNER); else if (!strcmp(name, "auth-filter")) ctx.cfg.auth_filter = cgit_new_filter(value, AUTH); else if (!strcmp(name, "embedded")) ctx.cfg.embedded = atoi(value); else if (!strcmp(name, "max-atom-items")) ctx.cfg.max_atom_items = atoi(value); else if (!strcmp(name, "max-message-length")) ctx.cfg.max_msg_len = atoi(value); else if (!strcmp(name, "max-repodesc-length")) ctx.cfg.max_repodesc_len = atoi(value); else if (!strcmp(name, "max-blob-size")) ctx.cfg.max_blob_size = atoi(value); else if (!strcmp(name, "max-repo-count")) ctx.cfg.max_repo_count = atoi(value); else if (!strcmp(name, "max-commit-count")) ctx.cfg.max_commit_count = atoi(value); else if (!strcmp(name, "project-list")) ctx.cfg.project_list = xstrdup(expand_macros(value)); else if (!strcmp(name, "scan-path")) if (!ctx.cfg.nocache && ctx.cfg.cache_size) process_cached_repolist(expand_macros(value)); else if (ctx.cfg.project_list) scan_projects(expand_macros(value), ctx.cfg.project_list, repo_config); else scan_tree(expand_macros(value), repo_config); else if (!strcmp(name, "scan-hidden-path")) ctx.cfg.scan_hidden_path = atoi(value); else if (!strcmp(name, "section-from-path")) ctx.cfg.section_from_path = atoi(value); else if (!strcmp(name, "repository-sort")) ctx.cfg.repository_sort = xstrdup(value); else if (!strcmp(name, "section-sort")) ctx.cfg.section_sort = atoi(value); else if (!strcmp(name, "source-filter")) ctx.cfg.source_filter = cgit_new_filter(value, SOURCE); else if (!strcmp(name, "summary-log")) ctx.cfg.summary_log = atoi(value); else if (!strcmp(name, "summary-branches")) ctx.cfg.summary_branches = atoi(value); else if (!strcmp(name, "summary-tags")) ctx.cfg.summary_tags = atoi(value); else if (!strcmp(name, "side-by-side-diffs")) ctx.cfg.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED; else if (!strcmp(name, "agefile")) ctx.cfg.agefile = xstrdup(value); else if (!strcmp(name, "mimetype-file")) ctx.cfg.mimetype_file = xstrdup(value); else if (!strcmp(name, "renamelimit")) ctx.cfg.renamelimit = atoi(value); else if (!strcmp(name, "remove-suffix")) ctx.cfg.remove_suffix = atoi(value); else if (!strcmp(name, "robots")) ctx.cfg.robots = xstrdup(value); else if (!strcmp(name, "clone-prefix")) ctx.cfg.clone_prefix = xstrdup(value); else if (!strcmp(name, "clone-url")) ctx.cfg.clone_url = xstrdup(value); else if (!strcmp(name, "local-time")) ctx.cfg.local_time = atoi(value); else if (!strcmp(name, "commit-sort")) { if (!strcmp(value, "date")) ctx.cfg.commit_sort = 1; if (!strcmp(value, "topo")) ctx.cfg.commit_sort = 2; } else if (!strcmp(name, "branch-sort")) { if (!strcmp(value, "age")) ctx.cfg.branch_sort = 1; if (!strcmp(value, "name")) ctx.cfg.branch_sort = 0; } else if (skip_prefix(name, "mimetype.", &arg)) add_mimetype(arg, value); else if (!strcmp(name, "include")) parse_configfile(expand_macros(value), config_cb); } static void querystring_cb(const char *name, const char *value) { if (!value) value = ""; if (!strcmp(name,"r")) { ctx.qry.repo = xstrdup(value); ctx.repo = cgit_get_repoinfo(value); } else if (!strcmp(name, "p")) { ctx.qry.page = xstrdup(value); } else if (!strcmp(name, "url")) { if (*value == '/') value++; ctx.qry.url = xstrdup(value); cgit_parse_url(value); } else if (!strcmp(name, "qt")) { ctx.qry.grep = xstrdup(value); } else if (!strcmp(name, "q")) { ctx.qry.search = xstrdup(value); } else if (!strcmp(name, "h")) { ctx.qry.head = xstrdup(value); ctx.qry.has_symref = 1; } else if (!strcmp(name, "id")) { ctx.qry.sha1 = xstrdup(value); ctx.qry.has_sha1 = 1; } else if (!strcmp(name, "id2")) { ctx.qry.sha2 = xstrdup(value); ctx.qry.has_sha1 = 1; } else if (!strcmp(name, "ofs")) { ctx.qry.ofs = atoi(value); } else if (!strcmp(name, "path")) { ctx.qry.path = trim_end(value, '/'); } else if (!strcmp(name, "name")) { ctx.qry.name = xstrdup(value); } else if (!strcmp(name, "s")) { ctx.qry.sort = xstrdup(value); } else if (!strcmp(name, "showmsg")) { ctx.qry.showmsg = atoi(value); } else if (!strcmp(name, "period")) { ctx.qry.period = xstrdup(value); } else if (!strcmp(name, "dt")) { ctx.qry.difftype = atoi(value); ctx.qry.has_difftype = 1; } else if (!strcmp(name, "ss")) { /* No longer generated, but there may be links out there. */ ctx.qry.difftype = atoi(value) ? DIFF_SSDIFF : DIFF_UNIFIED; ctx.qry.has_difftype = 1; } else if (!strcmp(name, "all")) { ctx.qry.show_all = atoi(value); } else if (!strcmp(name, "context")) { ctx.qry.context = atoi(value); } else if (!strcmp(name, "ignorews")) { ctx.qry.ignorews = atoi(value); } else if (!strcmp(name, "follow")) { ctx.qry.follow = atoi(value); } } static void prepare_context(void) { memset(&ctx, 0, sizeof(ctx)); ctx.cfg.agefile = "info/web/last-modified"; ctx.cfg.nocache = 0; ctx.cfg.cache_size = 0; ctx.cfg.cache_max_create_time = 5; ctx.cfg.cache_root = CGIT_CACHE_ROOT; ctx.cfg.cache_about_ttl = 15; ctx.cfg.cache_snapshot_ttl = 5; ctx.cfg.cache_repo_ttl = 5; ctx.cfg.cache_root_ttl = 5; ctx.cfg.cache_scanrc_ttl = 15; ctx.cfg.cache_dynamic_ttl = 5; ctx.cfg.cache_static_ttl = -1; ctx.cfg.case_sensitive_sort = 1; ctx.cfg.branch_sort = 0; ctx.cfg.commit_sort = 0; ctx.cfg.css = "/cgit.css"; ctx.cfg.logo = "/cgit.png"; ctx.cfg.favicon = "/favicon.ico"; ctx.cfg.local_time = 0; ctx.cfg.enable_http_clone = 1; ctx.cfg.enable_index_owner = 1; ctx.cfg.enable_tree_linenumbers = 1; ctx.cfg.enable_git_config = 0; ctx.cfg.max_repo_count = 50; ctx.cfg.max_commit_count = 50; ctx.cfg.max_lock_attempts = 5; ctx.cfg.max_msg_len = 80; ctx.cfg.max_repodesc_len = 80; ctx.cfg.max_blob_size = 0; ctx.cfg.max_stats = 0; ctx.cfg.project_list = NULL; ctx.cfg.renamelimit = -1; ctx.cfg.remove_suffix = 0; ctx.cfg.robots = "index, nofollow"; ctx.cfg.root_title = "Git repository browser"; ctx.cfg.root_desc = "a fast webinterface for the git dscm"; ctx.cfg.scan_hidden_path = 0; ctx.cfg.script_name = CGIT_SCRIPT_NAME; ctx.cfg.section = ""; ctx.cfg.repository_sort = "name"; ctx.cfg.section_sort = 1; ctx.cfg.summary_branches = 10; ctx.cfg.summary_log = 10; ctx.cfg.summary_tags = 10; ctx.cfg.max_atom_items = 10; ctx.cfg.difftype = DIFF_UNIFIED; ctx.env.cgit_config = getenv("CGIT_CONFIG"); ctx.env.http_host = getenv("HTTP_HOST"); ctx.env.https = getenv("HTTPS"); ctx.env.no_http = getenv("NO_HTTP"); ctx.env.path_info = getenv("PATH_INFO"); ctx.env.query_string = getenv("QUERY_STRING"); ctx.env.request_method = getenv("REQUEST_METHOD"); ctx.env.script_name = getenv("SCRIPT_NAME"); ctx.env.server_name = getenv("SERVER_NAME"); ctx.env.server_port = getenv("SERVER_PORT"); ctx.env.http_cookie = getenv("HTTP_COOKIE"); ctx.env.http_referer = getenv("HTTP_REFERER"); ctx.env.content_length = getenv("CONTENT_LENGTH") ? strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0; ctx.env.authenticated = 0; ctx.page.mimetype = "text/html"; ctx.page.charset = PAGE_ENCODING; ctx.page.filename = NULL; ctx.page.size = 0; ctx.page.modified = time(NULL); ctx.page.expires = ctx.page.modified; ctx.page.etag = NULL; memset(&ctx.cfg.mimetypes, 0, sizeof(struct string_list)); if (ctx.env.script_name) ctx.cfg.script_name = xstrdup(ctx.env.script_name); if (ctx.env.query_string) ctx.qry.raw = xstrdup(ctx.env.query_string); if (!ctx.env.cgit_config) ctx.env.cgit_config = CGIT_CONFIG; } struct refmatch { char *req_ref; char *first_ref; int match; }; static int find_current_ref(const char *refname, const struct object_id *oid, int flags, void *cb_data) { struct refmatch *info; info = (struct refmatch *)cb_data; if (!strcmp(refname, info->req_ref)) info->match = 1; if (!info->first_ref) info->first_ref = xstrdup(refname); return info->match; } static void free_refmatch_inner(struct refmatch *info) { if (info->first_ref) free(info->first_ref); } static char *find_default_branch(struct cgit_repo *repo) { struct refmatch info; char *ref; info.req_ref = repo->defbranch; info.first_ref = NULL; info.match = 0; for_each_branch_ref(find_current_ref, &info); if (info.match) ref = info.req_ref; else ref = info.first_ref; if (ref) ref = xstrdup(ref); free_refmatch_inner(&info); return ref; } static char *guess_defbranch(void) { const char *ref, *refname; struct object_id oid; ref = resolve_ref_unsafe("HEAD", 0, oid.hash, NULL); if (!ref || !skip_prefix(ref, "refs/heads/", &refname)) return "master"; return xstrdup(refname); } /* The caller must free filename and ref after calling this. */ static inline void parse_readme(const char *readme, char **filename, char **ref, struct cgit_repo *repo) { const char *colon; *filename = NULL; *ref = NULL; if (!readme || !readme[0]) return; /* Check if the readme is tracked in the git repo. */ colon = strchr(readme, ':'); if (colon && strlen(colon) > 1) { /* If it starts with a colon, we want to use * the default branch */ if (colon == readme && repo->defbranch) *ref = xstrdup(repo->defbranch); else *ref = xstrndup(readme, colon - readme); readme = colon + 1; } /* Prepend repo path to relative readme path unless tracked. */ if (!(*ref) && readme[0] != '/') *filename = fmtalloc("%s/%s", repo->path, readme); else *filename = xstrdup(readme); } static void choose_readme(struct cgit_repo *repo) { int found; char *filename, *ref; struct string_list_item *entry; if (!repo->readme.nr) return; found = 0; for_each_string_list_item(entry, &repo->readme) { parse_readme(entry->string, &filename, &ref, repo); if (!filename) { free(filename); free(ref); continue; } if (ref) { if (cgit_ref_path_exists(filename, ref, 1)) { found = 1; break; } } else if (!access(filename, R_OK)) { found = 1; break; } free(filename); free(ref); } repo->readme.strdup_strings = 1; string_list_clear(&repo->readme, 0); repo->readme.strdup_strings = 0; if (found) string_list_append(&repo->readme, filename)->util = ref; } static void print_no_repo_clone_urls(const char *url) { html(""); html_txt(url); html("\n"); } static int prepare_repo_cmd(void) { struct object_id oid; int nongit = 0; int rc; /* The path to the git repository. */ setenv("GIT_DIR", ctx.repo->path, 1); /* Do not look in /etc/ for gitconfig and gitattributes. */ setenv("GIT_CONFIG_NOSYSTEM", "1", 1); setenv("GIT_ATTR_NOSYSTEM", "1", 1); unsetenv("HOME"); unsetenv("XDG_CONFIG_HOME"); /* Setup the git directory and initialize the notes system. Both of these * load local configuration from the git repository, so we do them both while * the HOME variables are unset. */ setup_git_directory_gently(&nongit); init_display_notes(NULL); if (nongit) { const char *name = ctx.repo->name; rc = errno; ctx.page.title = fmtalloc("%s - %s", ctx.cfg.root_title, "config error"); ctx.repo = NULL; cgit_print_http_headers(); cgit_print_docstart(); cgit_print_pageheader(); cgit_print_error("Failed to open %s: %s", name, rc ? strerror(rc) : "Not a valid git repository"); cgit_print_docend(); return 1; } ctx.page.title = fmtalloc("%s - %s", ctx.repo->name, ctx.repo->desc); if (!ctx.repo->defbranch) ctx.repo->defbranch = guess_defbranch(); if (!ctx.qry.head) { ctx.qry.nohead = 1; ctx.qry.head = find_default_branch(ctx.repo); } if (!ctx.qry.head) { cgit_print_http_headers(); cgit_print_docstart(); cgit_print_pageheader(); cgit_print_error("Repository seems to be empty"); if (!strcmp(ctx.qry.page, "summary")) { html("\n"); cgit_prepare_repo_env(ctx.repo); cgit_add_clone_urls(print_no_repo_clone_urls); html("
 
Clone
\n"); } cgit_print_docend(); return 1; } if (get_oid(ctx.qry.head, &oid)) { char *old_head = ctx.qry.head; ctx.qry.head = xstrdup(ctx.repo->defbranch); cgit_print_error_page(404, "Not found", "Invalid branch: %s", old_head); free(old_head); return 1; } string_list_sort(&ctx.repo->submodules); cgit_prepare_repo_env(ctx.repo); choose_readme(ctx.repo); return 0; } static inline void open_auth_filter(const char *function) { cgit_open_filter(ctx.cfg.auth_filter, function, ctx.env.http_cookie ? ctx.env.http_cookie : "", ctx.env.request_method ? ctx.env.request_method : "", ctx.env.query_string ? ctx.env.query_string : "", ctx.env.http_referer ? ctx.env.http_referer : "", ctx.env.path_info ? ctx.env.path_info : "", ctx.env.http_host ? ctx.env.http_host : "", ctx.env.https ? ctx.env.https : "", ctx.qry.repo ? ctx.qry.repo : "", ctx.qry.page ? ctx.qry.page : "", ctx.qry.url ? ctx.qry.url : "", cgit_loginurl()); } /* We intentionally keep this rather small, instead of looping and * feeding it to the filter a couple bytes at a time. This way, the * filter itself does not need to handle any denial of service or * buffer bloat issues. If this winds up being too small, people * will complain on the mailing list, and we'll increase it as needed. */ #define MAX_AUTHENTICATION_POST_BYTES 4096 /* The filter is expected to spit out "Status: " and all headers. */ static inline void authenticate_post(void) { char buffer[MAX_AUTHENTICATION_POST_BYTES]; unsigned int len; open_auth_filter("authenticate-post"); len = ctx.env.content_length; if (len > MAX_AUTHENTICATION_POST_BYTES) len = MAX_AUTHENTICATION_POST_BYTES; if (read(STDIN_FILENO, buffer, len) < 0) die_errno("Could not read POST from stdin"); if (write(STDOUT_FILENO, buffer, len) < 0) die_errno("Could not write POST to stdout"); cgit_close_filter(ctx.cfg.auth_filter); exit(0); } static inline void authenticate_cookie(void) { /* If we don't have an auth_filter, consider all cookies valid, and thus return early. */ if (!ctx.cfg.auth_filter) { ctx.env.authenticated = 1; return; } /* If we're having something POST'd to /login, we're authenticating POST, * instead of the cookie, so call authenticate_post and bail out early. * This pattern here should match /?p=login with POST. */ if (ctx.env.request_method && ctx.qry.page && !ctx.repo && \ !strcmp(ctx.env.request_method, "POST") && !strcmp(ctx.qry.page, "login")) { authenticate_post(); return; } /* If we've made it this far, we're authenticating the cookie for real, so do that. */ open_auth_filter("authenticate-cookie"); ctx.env.authenticated = cgit_close_filter(ctx.cfg.auth_filter); } static void process_request(void) { struct cgit_cmd *cmd; /* If we're not yet authenticated, no matter what page we're on, * display the authentication body from the auth_filter. This should * never be cached. */ if (!ctx.env.authenticated) { ctx.page.title = "Authentication Required"; cgit_print_http_headers(); cgit_print_docstart(); cgit_print_pageheader(); open_auth_filter("body"); cgit_close_filter(ctx.cfg.auth_filter); cgit_print_docend(); return; } cmd = cgit_get_cmd(); if (!cmd) { ctx.page.title = "cgit error"; cgit_print_error_page(404, "Not found", "Invalid request"); return; } if (!ctx.cfg.enable_http_clone && cmd->is_clone) { ctx.page.title = "cgit error"; cgit_print_error_page(404, "Not found", "Invalid request"); return; } /* If cmd->want_vpath is set, assume ctx.qry.path contains a "virtual" * in-project path limit to be made available at ctx.qry.vpath. * Otherwise, no path limit is in effect (ctx.qry.vpath = NULL). */ ctx.qry.vpath = cmd->want_vpath ? ctx.qry.path : NULL; if (cmd->want_repo && !ctx.repo) { cgit_print_error_page(400, "Bad request", "No repository selected"); return; } if (ctx.repo && prepare_repo_cmd()) return; cmd->fn(); } static int cmp_repos(const void *a, const void *b) { const struct cgit_repo *ra = a, *rb = b; return strcmp(ra->url, rb->url); } static char *build_snapshot_setting(int bitmap) { const struct cgit_snapshot_format *f; struct strbuf result = STRBUF_INIT; for (f = cgit_snapshot_formats; f->suffix; f++) { if (f->bit & bitmap) { if (result.len) strbuf_addch(&result, ' '); strbuf_addstr(&result, f->suffix); } } return strbuf_detach(&result, NULL); } static char *get_first_line(char *txt) { char *t = xstrdup(txt); char *p = strchr(t, '\n'); if (p) *p = '\0'; return t; } static void print_repo(FILE *f, struct cgit_repo *repo) { struct string_list_item *item; fprintf(f, "repo.url=%s\n", repo->url); fprintf(f, "repo.name=%s\n", repo->name); fprintf(f, "repo.path=%s\n", repo->path); if (repo->owner) fprintf(f, "repo.owner=%s\n", repo->owner); if (repo->desc) { char *tmp = get_first_line(repo->desc); fprintf(f, "repo.desc=%s\n", tmp); free(tmp); } for_each_string_list_item(item, &repo->readme) { if (item->util) fprintf(f, "repo.readme=%s:%s\n", (char *)item->util, item->string); else fprintf(f, "repo.readme=%s\n", item->string); } if (repo->defbranch) fprintf(f, "repo.defbranch=%s\n", repo->defbranch); if (repo->module_link) fprintf(f, "repo.module-link=%s\n", repo->module_link); if (repo->section) fprintf(f, "repo.section=%s\n", repo->section); if (repo->homepage) fprintf(f, "repo.homepage=%s\n", repo->homepage); if (repo->clone_url) fprintf(f, "repo.clone-url=%s\n", repo->clone_url); fprintf(f, "repo.enable-commit-graph=%d\n", repo->enable_commit_graph); fprintf(f, "repo.enable-log-filecount=%d\n", repo->enable_log_filecount); fprintf(f, "repo.enable-log-linecount=%d\n", repo->enable_log_linecount); if (repo->about_filter && repo->about_filter != ctx.cfg.about_filter) cgit_fprintf_filter(repo->about_filter, f, "repo.about-filter="); if (repo->commit_filter && repo->commit_filter != ctx.cfg.commit_filter) cgit_fprintf_filter(repo->commit_filter, f, "repo.commit-filter="); if (repo->source_filter && repo->source_filter != ctx.cfg.source_filter) cgit_fprintf_filter(repo->source_filter, f, "repo.source-filter="); if (repo->email_filter && repo->email_filter != ctx.cfg.email_filter) cgit_fprintf_filter(repo->email_filter, f, "repo.email-filter="); if (repo->owner_filter && repo->owner_filter != ctx.cfg.owner_filter) cgit_fprintf_filter(repo->owner_filter, f, "repo.owner-filter="); if (repo->snapshots != ctx.cfg.snapshots) { char *tmp = build_snapshot_setting(repo->snapshots); fprintf(f, "repo.snapshots=%s\n", tmp ? tmp : ""); free(tmp); } if (repo->max_stats != ctx.cfg.max_stats) fprintf(f, "repo.max-stats=%s\n", cgit_find_stats_periodname(repo->max_stats)); if (repo->logo) fprintf(f, "repo.logo=%s\n", repo->logo); if (repo->logo_link) fprintf(f, "repo.logo-link=%s\n", repo->logo_link); fprintf(f, "repo.enable-remote-branches=%d\n", repo->enable_remote_branches); fprintf(f, "repo.enable-subject-links=%d\n", repo->enable_subject_links); fprintf(f, "repo.enable-html-serving=%d\n", repo->enable_html_serving); if (repo->branch_sort == 1) fprintf(f, "repo.branch-sort=age\n"); if (repo->commit_sort) { if (repo->commit_sort == 1) fprintf(f, "repo.commit-sort=date\n"); else if (repo->commit_sort == 2) fprintf(f, "repo.commit-sort=topo\n"); } fprintf(f, "repo.hide=%d\n", repo->hide); fprintf(f, "repo.ignore=%d\n", repo->ignore); fprintf(f, "\n"); } static void print_repolist(FILE *f, struct cgit_repolist *list, int start) { int i; for (i = start; i < list->count; i++) print_repo(f, &list->repos[i]); } /* Scan 'path' for git repositories, save the resulting repolist in 'cached_rc' * and return 0 on success. */ static int generate_cached_repolist(const char *path, const char *cached_rc) { struct strbuf locked_rc = STRBUF_INIT; int result = 0; int idx; FILE *f; strbuf_addf(&locked_rc, "%s.lock", cached_rc); f = fopen(locked_rc.buf, "wx"); if (!f) { /* Inform about the error unless the lockfile already existed, * since that only means we've got concurrent requests. */ result = errno; if (result != EEXIST) fprintf(stderr, "[cgit] Error opening %s: %s (%d)\n", locked_rc.buf, strerror(result), result); goto out; } idx = cgit_repolist.count; if (ctx.cfg.project_list) scan_projects(path, ctx.cfg.project_list, repo_config); else scan_tree(path, repo_config); print_repolist(f, &cgit_repolist, idx); if (rename(locked_rc.buf, cached_rc)) fprintf(stderr, "[cgit] Error renaming %s to %s: %s (%d)\n", locked_rc.buf, cached_rc, strerror(errno), errno); fclose(f); out: strbuf_release(&locked_rc); return result; } static void process_cached_repolist(const char *path) { struct stat st; struct strbuf cached_rc = STRBUF_INIT; time_t age; unsigned long hash; hash = hash_str(path); if (ctx.cfg.project_list) hash += hash_str(ctx.cfg.project_list); strbuf_addf(&cached_rc, "%s/rc-%8lx", ctx.cfg.cache_root, hash); if (stat(cached_rc.buf, &st)) { /* Nothing is cached, we need to scan without forking. And * if we fail to generate a cached repolist, we need to * invoke scan_tree manually. */ if (generate_cached_repolist(path, cached_rc.buf)) { if (ctx.cfg.project_list) scan_projects(path, ctx.cfg.project_list, repo_config); else scan_tree(path, repo_config); } goto out; } parse_configfile(cached_rc.buf, config_cb); /* If the cached configfile hasn't expired, lets exit now */ age = time(NULL) - st.st_mtime; if (age <= (ctx.cfg.cache_scanrc_ttl * 60)) goto out; /* The cached repolist has been parsed, but it was old. So lets * rescan the specified path and generate a new cached repolist * in a child-process to avoid latency for the current request. */ if (fork()) goto out; exit(generate_cached_repolist(path, cached_rc.buf)); out: strbuf_release(&cached_rc); } static void cgit_parse_args(int argc, const char **argv) { int i; const char *arg; int scan = 0; for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "--version")) { printf("CGit %s | https://git.zx2c4.com/cgit/\n\nCompiled in features:\n", CGIT_VERSION); #ifdef NO_LUA printf("[-] "); #else printf("[+] "); #endif printf("Lua scripting\n"); #ifndef HAVE_LINUX_SENDFILE printf("[-] "); #else printf("[+] "); #endif printf("Linux sendfile() usage\n"); exit(0); } if (skip_prefix(argv[i], "--cache=", &arg)) { ctx.cfg.cache_root = xstrdup(arg); } else if (!strcmp(argv[i], "--nocache")) { ctx.cfg.nocache = 1; } else if (!strcmp(argv[i], "--nohttp")) { ctx.env.no_http = "1"; } else if (skip_prefix(argv[i], "--query=", &arg)) { ctx.qry.raw = xstrdup(arg); } else if (skip_prefix(argv[i], "--repo=", &arg)) { ctx.qry.repo = xstrdup(arg); } else if (skip_prefix(argv[i], "--page=", &arg)) { ctx.qry.page = xstrdup(arg); } else if (skip_prefix(argv[i], "--head=", &arg)) { ctx.qry.head = xstrdup(arg); ctx.qry.has_symref = 1; } else if (skip_prefix(argv[i], "--sha1=", &arg)) { ctx.qry.sha1 = xstrdup(arg); ctx.qry.has_sha1 = 1; } else if (skip_prefix(argv[i], "--ofs=", &arg)) { ctx.qry.ofs = atoi(arg); } else if (skip_prefix(argv[i], "--scan-tree=", &arg) || skip_prefix(argv[i], "--scan-path=", &arg)) { /* * HACK: The global snapshot bit mask defines the set * of allowed snapshot formats, but the config file * hasn't been parsed yet so the mask is currently 0. * By setting all bits high before scanning we make * sure that any in-repo cgitrc snapshot setting is * respected by scan_tree(). * * NOTE: We assume that there aren't more than 8 * different snapshot formats supported by cgit... */ ctx.cfg.snapshots = 0xFF; scan++; scan_tree(arg, repo_config); } } if (scan) { qsort(cgit_repolist.repos, cgit_repolist.count, sizeof(struct cgit_repo), cmp_repos); print_repolist(stdout, &cgit_repolist, 0); exit(0); } } static int calc_ttl(void) { if (!ctx.repo) return ctx.cfg.cache_root_ttl; if (!ctx.qry.page) return ctx.cfg.cache_repo_ttl; if (!strcmp(ctx.qry.page, "about")) return ctx.cfg.cache_about_ttl; if (!strcmp(ctx.qry.page, "snapshot")) return ctx.cfg.cache_snapshot_ttl; if (ctx.qry.has_sha1) return ctx.cfg.cache_static_ttl; if (ctx.qry.has_symref) return ctx.cfg.cache_dynamic_ttl; return ctx.cfg.cache_repo_ttl; } int cmd_main(int argc, const char **argv) { const char *path; int err, ttl; cgit_init_filters(); atexit(cgit_cleanup_filters); prepare_context(); cgit_repolist.length = 0; cgit_repolist.count = 0; cgit_repolist.repos = NULL; cgit_parse_args(argc, argv); parse_configfile(expand_macros(ctx.env.cgit_config), config_cb); ctx.repo = NULL; http_parse_querystring(ctx.qry.raw, querystring_cb); /* If virtual-root isn't specified in cgitrc, lets pretend * that virtual-root equals SCRIPT_NAME, minus any possibly * trailing slashes. */ if (!ctx.cfg.virtual_root && ctx.cfg.script_name) ctx.cfg.virtual_root = ensure_end(ctx.cfg.script_name, '/'); /* If no url parameter is specified on the querystring, lets * use PATH_INFO as url. This allows cgit to work with virtual * urls without the need for rewriterules in the webserver (as * long as PATH_INFO is included in the cache lookup key). */ path = ctx.env.path_info; if (!ctx.qry.url && path) { if (path[0] == '/') path++; ctx.qry.url = xstrdup(path); if (ctx.qry.raw) { char *newqry = fmtalloc("%s?%s", path, ctx.qry.raw); free(ctx.qry.raw); ctx.qry.raw = newqry; } else ctx.qry.raw = xstrdup(ctx.qry.url); cgit_parse_url(ctx.qry.url); } /* Before we go any further, we set ctx.env.authenticated by checking to see * if the supplied cookie is valid. All cookies are valid if there is no * auth_filter. If there is an auth_filter, the filter decides. */ authenticate_cookie(); ttl = calc_ttl(); if (ttl < 0) ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */ else ctx.page.expires += ttl * 60; if (!ctx.env.authenticated || (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD"))) ctx.cfg.nocache = 1; if (ctx.cfg.nocache) ctx.cfg.cache_size = 0; err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, ctx.qry.raw, ttl, process_request); cgit_cleanup_filters(); if (err) cgit_print_error("Error processing page: %s (%d)", strerror(err), err); return err; } cgit-1.1/cgit.css000066400000000000000000000322041301521500400137310ustar00rootroot00000000000000div#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.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.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.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.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; } cgit-1.1/cgit.h000066400000000000000000000215001301521500400133650ustar00rootroot00000000000000#ifndef CGIT_H #define CGIT_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* 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" 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 pipe_fh[2]; int pid; }; struct cgit_repo { char *url; char *name; char *path; char *desc; char *owner; char *homepage; char *defbranch; char *module_link; struct string_list readme; char *section; char *clone_url; char *logo; char *logo_link; int snapshots; 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 *index_header; char *index_info; 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_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 nocache; 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; int bit; }; 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 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 */ cgit-1.1/cgit.mk000066400000000000000000000104611301521500400135510ustar00rootroot00000000000000# 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/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-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) cgit-1.1/cgit.png000066400000000000000000000025261301521500400137310ustar00rootroot00000000000000PNG  IHDR`@<sRGB pHYs  PLTEb=tRNS %(./1345689;BDFGIJOSVWXZ[\]^_`acdefjlmopqrsuwxyz{|~ա-bKGDHIDATX[QLʲŶ1Ċ3m=mKiEH4 O1Ky=b)Pˀ (wU?:x'Rx߱9_A¼Ak4~F`8U$@Kkgp](&EQ%Eggnͻ.jH6Ś) E STR~QX#ǁ@ZS@¥e4 QJÀf^Po?^^Ti`"YETעCus=).5O@1cEgz%_c@@z^^У?hzMᶬbU7 h5%8<v*J{`:w='>v3'`T1՜䛦Vr#ЈXSRy'm˿aԟNҐT}YTvwp.ZsHuWU?ȏ00%L4U+q|j4;nx*0 p];:ntrfnيgDWxFpƸ-*wF 꿶KtUB/ǭr[}g<-1j)?P?zͅp}IENDB`cgit-1.1/cgitrc.5.txt000066400000000000000000001005141301521500400144500ustar00rootroot00000000000000:man source: cgit :man manual: cgit CGITRC(5) ======== NAME ---- cgitrc - runtime configuration for cgit SYNOPSIS -------- Cgitrc contains all runtime settings for cgit, including the list of git repositories, formatted as a line-separated list of NAME=VALUE pairs. Blank lines, and lines starting with '#', are ignored. LOCATION -------- The default location of cgitrc, defined at compile time, is /etc/cgitrc. At runtime, cgit will consult the environment variable CGIT_CONFIG and, if defined, use its value instead. GLOBAL SETTINGS --------------- about-filter:: Specifies a command which will be invoked to format the content of about pages (both top-level and for each repository). The command will get the content of the about-file on its STDIN, the name of the file as the first argument, and the STDOUT from the command will be included verbatim on the about page. Default value: none. See also: "FILTER API". agefile:: Specifies a path, relative to each repository path, which can be used to specify the date and time of the youngest commit in the repository. The first line in the file is used as input to the "parse_date" function in libgit. Recommended timestamp-format is "yyyy-mm-dd hh:mm:ss". You may want to generate this file from a post-receive hook. Default value: "info/web/last-modified". auth-filter:: Specifies a command that will be invoked for authenticating repository access. Receives quite a few arguments, and data on both stdin and stdout for authentication processing. Details follow later in this document. If no auth-filter is specified, no authentication is performed. Default value: none. See also: "FILTER API". branch-sort:: Flag which, when set to "age", enables date ordering in the branch ref list, and when set to "name" enables ordering by branch name. Default value: "name". cache-root:: Path used to store the cgit cache entries. Default value: "/var/cache/cgit". See also: "MACRO EXPANSION". cache-static-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of repository pages accessed with a fixed SHA1. See also: "CACHE". Default value: -1". cache-dynamic-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of repository pages accessed without a fixed SHA1. See also: "CACHE". Default value: "5". cache-repo-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of the repository summary page. See also: "CACHE". Default value: "5". cache-root-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of the repository index page. See also: "CACHE". Default value: "5". cache-scanrc-ttl:: Number which specifies the time-to-live, in minutes, for the result of scanning a path for git repositories. See also: "CACHE". Default value: "15". cache-about-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of the repository about page. See also: "CACHE". Default value: "15". cache-snapshot-ttl:: Number which specifies the time-to-live, in minutes, for the cached version of snapshots. See also: "CACHE". Default value: "5". cache-size:: The maximum number of entries in the cgit cache. When set to "0", caching is disabled. See also: "CACHE". Default value: "0" case-sensitive-sort:: Sort items in the repo list case sensitively. Default value: "1". See also: repository-sort, section-sort. clone-prefix:: Space-separated list of common prefixes which, when combined with a repository url, generates valid clone urls for the repository. This setting is only used if `repo.clone-url` is unspecified. Default value: none. clone-url:: Space-separated list of clone-url templates. This setting is only used if `repo.clone-url` is unspecified. Default value: none. See also: "MACRO EXPANSION", "FILTER API". commit-filter:: Specifies a command which will be invoked to format commit messages. The command will get the message on its STDIN, and the STDOUT from the command will be included verbatim as the commit message, i.e. this can be used to implement bugtracker integration. Default value: none. See also: "FILTER API". commit-sort:: Flag which, when set to "date", enables strict date ordering in the commit log, and when set to "topo" enables strict topological ordering. If unset, the default ordering of "git log" is used. Default value: unset. css:: Url which specifies the css document to include in all cgit pages. Default value: "/cgit.css". email-filter:: Specifies a command which will be invoked to format names and email address of committers, authors, and taggers, as represented in various places throughout the cgit interface. This command will receive an email address and an origin page string as its command line arguments, and the text to format on STDIN. It is to write the formatted text back out onto STDOUT. Default value: none. See also: "FILTER API". embedded:: Flag which, when set to "1", will make cgit generate a html fragment suitable for embedding in other html pages. Default value: none. See also: "noheader". enable-commit-graph:: Flag which, when set to "1", will make cgit print an ASCII-art commit history graph to the left of the commit messages in the repository log page. Default value: "0". enable-filter-overrides:: Flag which, when set to "1", allows all filter settings to be overridden in repository-specific cgitrc files. Default value: none. enable-follow-links:: Flag which, when set to "1", allows users to follow a file in the log view. Default value: "0". enable-http-clone:: If set to "1", cgit will act as an dumb HTTP endpoint for git clones. You can add "http://$HTTP_HOST$SCRIPT_NAME/$CGIT_REPO_URL" to clone-url to expose this feature. If you use an alternate way of serving git repositories, you may wish to disable this. Default value: "1". enable-index-links:: Flag which, when set to "1", will make cgit generate extra links for each repo in the repository index (specifically, to the "summary", "commit" and "tree" pages). Default value: "0". enable-index-owner:: Flag which, when set to "1", will make cgit display the owner of each repo in the repository index. Default value: "1". enable-log-filecount:: Flag which, when set to "1", will make cgit print the number of modified files for each commit on the repository log page. Default value: "0". enable-log-linecount:: Flag which, when set to "1", will make cgit print the number of added and removed lines for each commit on the repository log page. Default value: "0". enable-remote-branches:: Flag which, when set to "1", will make cgit display remote branches in the summary and refs views. Default value: "0". See also: "repo.enable-remote-branches". enable-subject-links:: Flag which, when set to "1", will make cgit use the subject of the parent commit as link text when generating links to parent commits in commit view. Default value: "0". See also: "repo.enable-subject-links". enable-html-serving:: Flag which, when set to "1", will allow the /plain handler to serve mimetype headers that result in the file being treated as HTML by the browser. When set to "0", such file types are returned instead as text/plain or application/octet-stream. Default value: "0". See also: "repo.enable-html-serving". enable-tree-linenumbers:: Flag which, when set to "1", will make cgit generate linenumber links for plaintext blobs printed in the tree view. Default value: "1". enable-git-config:: Flag which, when set to "1", will allow cgit to use git config to set any repo specific settings. This option is used in conjunction with "scan-path", and must be defined prior, to augment repo-specific settings. The keys gitweb.owner, gitweb.category, gitweb.description, and gitweb.homepage will map to the cgit keys repo.owner, repo.section, repo.desc, and repo.homepage respectively. All git config keys that begin with "cgit." will be mapped to the corresponding "repo." key in cgit. Default value: "0". See also: scan-path, section-from-path. favicon:: Url used as link to a shortcut icon for cgit. It is suggested to use the value "/favicon.ico" since certain browsers will ignore other values. Default value: "/favicon.ico". footer:: The content of the file specified with this option will be included verbatim at the bottom of all pages (i.e. it replaces the standard "generated by..." message. Default value: none. head-include:: The content of the file specified with this option will be included verbatim in the html HEAD section on all pages. Default value: none. header:: The content of the file specified with this option will be included verbatim at the top of all pages. Default value: none. include:: Name of a configfile to include before the rest of the current config- file is parsed. Default value: none. See also: "MACRO EXPANSION". index-header:: The content of the file specified with this option will be included verbatim above the repository index. This setting is deprecated, and will not be supported by cgit-1.0 (use root-readme instead). Default value: none. index-info:: The content of the file specified with this option will be included verbatim below the heading on the repository index page. This setting is deprecated, and will not be supported by cgit-1.0 (use root-desc instead). Default value: none. local-time:: Flag which, if set to "1", makes cgit print commit and tag times in the servers timezone. Default value: "0". logo:: Url which specifies the source of an image which will be used as a logo on all cgit pages. Default value: "/cgit.png". logo-link:: Url loaded when clicking on the cgit logo image. If unspecified the calculated url of the repository index page will be used. Default value: none. owner-filter:: Specifies a command which will be invoked to format the Owner column of the main page. The command will get the owner on STDIN, and the STDOUT from the command will be included verbatim in the table. This can be used to link to additional context such as an owners home page. When active this filter is used instead of the default owner query url. Default value: none. See also: "FILTER API". max-atom-items:: Specifies the number of items to display in atom feeds view. Default value: "10". max-commit-count:: Specifies the number of entries to list per page in "log" view. Default value: "50". max-message-length:: Specifies the maximum number of commit message characters to display in "log" view. Default value: "80". max-repo-count:: Specifies the number of entries to list per page on the repository index page. Default value: "50". max-repodesc-length:: Specifies the maximum number of repo description characters to display on the repository index page. Default value: "80". max-blob-size:: Specifies the maximum size of a blob to display HTML for in KBytes. Default value: "0" (limit disabled). max-stats:: Set the default maximum statistics period. Valid values are "week", "month", "quarter" and "year". If unspecified, statistics are disabled. Default value: none. See also: "repo.max-stats". mimetype.:: Set the mimetype for the specified filename extension. This is used by the `plain` command when returning blob content. mimetype-file:: Specifies the file to use for automatic mimetype lookup. If specified then this field is used as a fallback when no "mimetype." match is found. If unspecified then no such lookup is performed. The typical file to use on a Linux system is /etc/mime.types. The format of the file must comply to: - a comment line is an empty line or a line starting with a hash (#), optionally preceded by whitespace - a non-comment line starts with the mimetype (like image/png), followed by one or more file extensions (like jpg), all separated by whitespace Default value: none. See also: "mimetype.". module-link:: Text which will be used as the formatstring for a hyperlink when a submodule is printed in a directory listing. The arguments for the formatstring are the path and SHA1 of the submodule commit. Default value: none. nocache:: If set to the value "1" caching will be disabled. This settings is deprecated, and will not be honored starting with cgit-1.0. Default value: "0". noplainemail:: If set to "1" showing full author email addresses will be disabled. Default value: "0". noheader:: Flag which, when set to "1", will make cgit omit the standard header on all pages. Default value: none. See also: "embedded". project-list:: A list of subdirectories inside of scan-path, relative to it, that should loaded as git repositories. This must be defined prior to scan-path. Default value: none. See also: scan-path, "MACRO EXPANSION". readme:: Text which will be used as default value for "repo.readme". Multiple config keys may be specified, and cgit will use the first found file in this list. This is useful in conjunction with scan-path. Default value: none. See also: scan-path, repo.readme. remove-suffix:: If set to "1" and scan-path is enabled, if any repositories are found with a suffix of ".git", this suffix will be removed for the url and name. This must be defined prior to scan-path. Default value: "0". See also: scan-path. renamelimit:: Maximum number of files to consider when detecting renames. The value "-1" uses the compiletime value in git (for further info, look at `man git-diff`). Default value: "-1". repo.group:: Legacy alias for "section". This option is deprecated and will not be supported in cgit-1.0. repository-sort:: The way in which repositories in each section are sorted. Valid values are "name" for sorting by the repo name or "age" for sorting by the most recently updated repository. Default value: "name". See also: section, case-sensitive-sort, section-sort. robots:: Text used as content for the "robots" meta-tag. Default value: "index, nofollow". root-desc:: Text printed below the heading on the repository index page. Default value: "a fast webinterface for the git dscm". root-readme:: The content of the file specified with this option will be included verbatim below the "about" link on the repository index page. Default value: none. root-title:: Text printed as heading on the repository index page. Default value: "Git Repository Browser". scan-hidden-path:: If set to "1" and scan-path is enabled, scan-path will recurse into directories whose name starts with a period ('.'). Otherwise, scan-path will stay away from such directories (considered as "hidden"). Note that this does not apply to the ".git" directory in non-bare repos. This must be defined prior to scan-path. Default value: 0. See also: scan-path. scan-path:: A path which will be scanned for repositories. If caching is enabled, the result will be cached as a cgitrc include-file in the cache directory. If project-list has been defined prior to scan-path, scan-path loads only the directories listed in the file pointed to by project-list. Be advised that only the global settings taken before the scan-path directive will be applied to each repository. Default value: none. See also: cache-scanrc-ttl, project-list, "MACRO EXPANSION". section:: The name of the current repository section - all repositories defined after this option will inherit the current section name. Default value: none. section-sort:: Flag which, when set to "1", will sort the sections on the repository listing by name. Set this flag to "0" if the order in the cgitrc file should be preserved. Default value: "1". See also: section, case-sensitive-sort, repository-sort. section-from-path:: A number which, if defined prior to scan-path, specifies how many path elements from each repo path to use as a default section name. If negative, cgit will discard the specified number of path elements above the repo directory. Default value: "0". side-by-side-diffs:: If set to "1" shows side-by-side diffs instead of unidiffs per default. Default value: "0". snapshots:: Text which specifies the default set of snapshot formats that cgit generates links for. The value is a space-separated list of zero or more of the values "tar", "tar.gz", "tar.bz2", "tar.xz" and "zip". Default value: none. source-filter:: Specifies a command which will be invoked to format plaintext blobs in the tree view. The command will get the blob content on its STDIN and the name of the blob as its only command line argument. The STDOUT from the command will be included verbatim as the blob contents, i.e. this can be used to implement e.g. syntax highlighting. Default value: none. See also: "FILTER API". summary-branches:: Specifies the number of branches to display in the repository "summary" view. Default value: "10". summary-log:: Specifies the number of log entries to display in the repository "summary" view. Default value: "10". summary-tags:: Specifies the number of tags to display in the repository "summary" view. Default value: "10". strict-export:: Filename which, if specified, needs to be present within the repository for cgit to allow access to that repository. This can be used to emulate gitweb's EXPORT_OK and STRICT_EXPORT functionality and limit cgit's repositories to match those exported by git-daemon. This option must be defined prior to scan-path. virtual-root:: Url which, if specified, will be used as root for all cgit links. It will also cause cgit to generate 'virtual urls', i.e. urls like '/cgit/tree/README' as opposed to '?r=cgit&p=tree&path=README'. Default value: none. NOTE: cgit has recently learned how to use PATH_INFO to achieve the same kind of virtual urls, so this option will probably be deprecated. REPOSITORY SETTINGS ------------------- repo.about-filter:: Override the default about-filter. Default value: none. See also: "enable-filter-overrides". See also: "FILTER API". repo.branch-sort:: Flag which, when set to "age", enables date ordering in the branch ref list, and when set to "name" enables ordering by branch name. Default value: "name". repo.clone-url:: A list of space-separated urls which can be used to clone this repo. Default value: none. See also: "MACRO EXPANSION". repo.commit-filter:: Override the default commit-filter. Default value: none. See also: "enable-filter-overrides". See also: "FILTER API". repo.commit-sort:: Flag which, when set to "date", enables strict date ordering in the commit log, and when set to "topo" enables strict topological ordering. If unset, the default ordering of "git log" is used. Default value: unset. repo.defbranch:: The name of the default branch for this repository. If no such branch exists in the repository, the first branch name (when sorted) is used as default instead. Default value: branch pointed to by HEAD, or "master" if there is no suitable HEAD. repo.desc:: The value to show as repository description. Default value: none. repo.homepage:: The value to show as repository homepage. Default value: none. repo.email-filter:: Override the default email-filter. Default value: none. See also: "enable-filter-overrides". See also: "FILTER API". repo.enable-commit-graph:: A flag which can be used to disable the global setting `enable-commit-graph'. Default value: none. repo.enable-log-filecount:: A flag which can be used to disable the global setting `enable-log-filecount'. Default value: none. repo.enable-log-linecount:: A flag which can be used to disable the global setting `enable-log-linecount'. Default value: none. repo.enable-remote-branches:: Flag which, when set to "1", will make cgit display remote branches in the summary and refs views. Default value: . repo.enable-subject-links:: A flag which can be used to override the global setting `enable-subject-links'. Default value: none. repo.enable-html-serving:: A flag which can be used to override the global setting `enable-html-serving`. Default value: none. repo.hide:: Flag which, when set to "1", hides the repository from the repository index. The repository can still be accessed by providing a direct path. Default value: "0". See also: "repo.ignore". repo.ignore:: Flag which, when set to "1", ignores the repository. The repository is not shown in the index and cannot be accessed by providing a direct path. Default value: "0". See also: "repo.hide". repo.logo:: Url which specifies the source of an image which will be used as a logo on this repo's pages. Default value: global logo. repo.logo-link:: Url loaded when clicking on the cgit logo image. If unspecified the calculated url of the repository index page will be used. Default value: global logo-link. repo.owner-filter:: Override the default owner-filter. Default value: none. See also: "enable-filter-overrides". See also: "FILTER API". repo.module-link:: Text which will be used as the formatstring for a hyperlink when a submodule is printed in a directory listing. The arguments for the formatstring are the path and SHA1 of the submodule commit. Default value: repo.module-link.:: Text which will be used as the formatstring for a hyperlink when a submodule with the specified subdirectory path is printed in a directory listing. The only argument for the formatstring is the SHA1 of the submodule commit. Default value: none. repo.max-stats:: Override the default maximum statistics period. Valid values are equal to the values specified for the global "max-stats" setting. Default value: none. repo.name:: The value to show as repository name. Default value: . repo.owner:: A value used to identify the owner of the repository. Default value: none. repo.path:: An absolute path to the repository directory. For non-bare repositories this is the .git-directory. Default value: none. repo.readme:: A path (relative to ) which specifies a file to include verbatim as the "About" page for this repo. You may also specify a git refspec by head or by hash by prepending the refspec followed by a colon. For example, "master:docs/readme.mkd". If the value begins with a colon, i.e. ":docs/readme.rst", the default branch of the repository will be used. Sharing any file will expose that entire directory tree to the "/about/PATH" endpoints, so be sure that there are no non-public files located in the same directory as the readme file. Default value: . repo.snapshots:: A mask of snapshot formats for this repo that cgit generates links for, restricted by the global "snapshots" setting. Default value: . repo.section:: Override the current section name for this repository. Default value: none. repo.source-filter:: Override the default source-filter. Default value: none. See also: "enable-filter-overrides". See also: "FILTER API". repo.url:: The relative url used to access the repository. This must be the first setting specified for each repo. Default value: none. REPOSITORY-SPECIFIC CGITRC FILE ------------------------------- When the option "scan-path" is used to auto-discover git repositories, cgit will try to parse the file "cgitrc" within any found repository. Such a repo-specific config file may contain any of the repo-specific options described above, except "repo.url" and "repo.path". Additionally, the "filter" options are only acknowledged in repo-specific config files when "enable-filter-overrides" is set to "1". Note: the "repo." prefix is dropped from the option names in repo-specific config files, e.g. "repo.desc" becomes "desc". FILTER API ---------- By default, filters are separate processes that are executed each time they are needed. Alternative technologies may be used by prefixing the filter specification with the relevant string; available values are: 'exec:':: The default "one process per filter" mode. 'lua:':: Executes the script using a built-in Lua interpreter. The script is loaded once per execution of cgit, and may be called multiple times during cgit's lifetime, making it a good choice for repeated filters such as the 'email filter'. It responds to three functions: 'filter_open(argument1, argument2, argument3, ...)':: This is called upon activation of the filter for a particular set of data. 'filter_write(buffer)':: This is called whenever cgit writes data to the webpage. 'filter_close()':: This is called when the current filtering operation is completed. It must return an integer value. Usually 0 indicates success. Additionally, cgit exposes to the Lua the following built-in functions: 'html(str)':: Writes 'str' to the webpage. 'html_txt(str)':: HTML escapes and writes 'str' to the webpage. 'html_attr(str)':: HTML escapes for an attribute and writes "str' to the webpage. 'html_url_path(str)':: URL escapes for a path and writes 'str' to the webpage. 'html_url_arg(str)':: URL escapes for an argument and writes 'str' to the webpage. 'html_include(file)':: Includes 'file' in webpage. Parameters are provided to filters as follows. about filter:: This filter is given a single parameter: the filename of the source file to filter. The filter can use the filename to determine (for example) the type of syntax to follow when formatting the readme file. The about text that is to be filtered is available on standard input and the filtered text is expected on standard output. commit filter:: This filter is given no arguments. The commit message text that is to be filtered is available on standard input and the filtered text is expected on standard output. email filter:: This filter is given two parameters: the email address of the relevant author and a string indicating the originating page. The filter will then receive the text string to format on standard input and is expected to write to standard output the formatted text to be included in the page. owner filter:: This filter is given no arguments. The owner text is available on standard input and the filter is expected to write to standard output. The output is included in the Owner column. source filter:: This filter is given a single parameter: the filename of the source file to filter. The filter can use the filename to determine (for example) the syntax highlighting mode. The contents of the source file that is to be filtered is available on standard input and the filtered contents is expected on standard output. auth filter:: The authentication filter receives 12 parameters: - filter action, explained below, which specifies which action the filter is called for - http cookie - http method - http referer - http path - http https flag - cgit repo - cgit page - cgit url - cgit login url When the filter action is "body", this filter must write to output the HTML for displaying the login form, which POSTs to the login url. When the filter action is "authenticate-cookie", this filter must validate the http cookie and return a 0 if it is invalid or 1 if it is invalid, in the exit code / close function. If the filter action is "authenticate-post", this filter receives POST'd parameters on standard input, and should write a complete CGI response, preferably with a 302 redirect, and write to output one or more "Set-Cookie" HTTP headers, each followed by a newline. Please see `filters/simple-authentication.lua` for a clear example script that may be modified. All filters are handed the following environment variables: - CGIT_REPO_URL (from repo.url) - CGIT_REPO_NAME (from repo.name) - CGIT_REPO_PATH (from repo.path) - CGIT_REPO_OWNER (from repo.owner) - CGIT_REPO_DEFBRANCH (from repo.defbranch) - CGIT_REPO_SECTION (from repo.section) - CGIT_REPO_CLONE_URL (from repo.clone-url) If a setting is not defined for a repository and the corresponding global setting is also not defined (if applicable), then the corresponding environment variable will be unset. MACRO EXPANSION --------------- The following cgitrc options support a simple macro expansion feature, where tokens prefixed with "$" are replaced with the value of a similarly named environment variable: - cache-root - include - project-list - scan-path Macro expansion will also happen on the content of $CGIT_CONFIG, if defined. One usage of this feature is virtual hosting, which in its simplest form can be accomplished by adding the following line to /etc/cgitrc: include=/etc/cgitrc.d/$HTTP_HOST The following options are expanded during request processing, and support the environment variables defined in "FILTER API": - clone-url - repo.clone-url CACHE ------ All cache ttl values are in minutes. Negative ttl values indicate that a page type will never expire, and thus the first time a URL is accessed, the result will be cached indefinitely, even if the underlying git repository changes. Conversely, when a ttl value is zero, the cache is disabled for that particular page type, and the page type is never cached. EXAMPLE CGITRC FILE ------------------- .... # Enable caching of up to 1000 output entries cache-size=1000 # Specify some default clone urls using macro expansion clone-url=git://foo.org/$CGIT_REPO_URL git@foo.org:$CGIT_REPO_URL # Specify the css url css=/css/cgit.css # Show owner on index page enable-index-owner=1 # Allow http transport git clone enable-http-clone=1 # Show extra links for each repository on the index page enable-index-links=1 # Enable ASCII art commit history graph on the log pages enable-commit-graph=1 # Show number of affected files per commit on the log pages enable-log-filecount=1 # Show number of added/removed lines per commit on the log pages enable-log-linecount=1 # Sort branches by date branch-sort=age # Add a cgit favicon favicon=/favicon.ico # Use a custom logo logo=/img/mylogo.png # Enable statistics per week, month and quarter max-stats=quarter # Set the title and heading of the repository index page root-title=example.com git repositories # Set a subheading for the repository index page root-desc=tracking the foobar development # Include some more info about example.com on the index page root-readme=/var/www/htdocs/about.html # Allow download of tar.gz, tar.bz2 and zip-files snapshots=tar.gz tar.bz2 zip ## ## List of common mimetypes ## mimetype.gif=image/gif mimetype.html=text/html mimetype.jpg=image/jpeg mimetype.jpeg=image/jpeg mimetype.pdf=application/pdf mimetype.png=image/png mimetype.svg=image/svg+xml # Highlight source code with python pygments-based highlighter source-filter=/var/www/cgit/filters/syntax-highlighting.py # Format markdown, restructuredtext, manpages, text files, and html files # through the right converters about-filter=/var/www/cgit/filters/about-formatting.sh ## ## Search for these files in the root of the default branch of repositories ## for coming up with the about page: ## readme=:README.md readme=:readme.md readme=:README.mkd readme=:readme.mkd readme=:README.rst readme=:readme.rst readme=:README.html readme=:readme.html readme=:README.htm readme=:readme.htm readme=:README.txt readme=:readme.txt readme=:README readme=:readme readme=:INSTALL.md readme=:install.md readme=:INSTALL.mkd readme=:install.mkd readme=:INSTALL.rst readme=:install.rst readme=:INSTALL.html readme=:install.html readme=:INSTALL.htm readme=:install.htm readme=:INSTALL.txt readme=:install.txt readme=:INSTALL readme=:install ## ## List of repositories. ## PS: Any repositories listed when section is unset will not be ## displayed under a section heading ## PPS: This list could be kept in a different file (e.g. '/etc/cgitrepos') ## and included like this: ## include=/etc/cgitrepos ## repo.url=foo repo.path=/pub/git/foo.git repo.desc=the master foo repository repo.owner=fooman@example.com repo.readme=info/web/about.html repo.url=bar repo.path=/pub/git/bar.git repo.desc=the bars for your foo repo.owner=barman@example.com repo.readme=info/web/about.html # The next repositories will be displayed under the 'extras' heading section=extras repo.url=baz repo.path=/pub/git/baz.git repo.desc=a set of extensions for bar users repo.url=wiz repo.path=/pub/git/wiz.git repo.desc=the wizard of foo # Add some mirrored repositories section=mirrors repo.url=git repo.path=/pub/git/git.git repo.desc=the dscm repo.url=linux repo.path=/pub/git/linux.git repo.desc=the kernel # Disable adhoc downloads of this repo repo.snapshots=0 # Disable line-counts for this repo repo.enable-log-linecount=0 # Restrict the max statistics period for this repo repo.max-stats=month .... BUGS ---- Comments currently cannot appear on the same line as a setting; the comment will be included as part of the value. E.g. this line: robots=index # allow indexing will generate the following html element: AUTHOR ------ Lars Hjemli Jason A. Donenfeld cgit-1.1/cmd.c000066400000000000000000000077341301521500400132120ustar00rootroot00000000000000/* cmd.c: the cgit command dispatcher * * Copyright (C) 2006-2014 cgit Development Team * * 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-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 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(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; } cgit-1.1/cmd.h000066400000000000000000000003641301521500400132070ustar00rootroot00000000000000#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 */ cgit-1.1/configfile.c000066400000000000000000000031141301521500400145400ustar00rootroot00000000000000/* configfile.c: parsing of config files * * Copyright (C) 2006-2014 cgit Development Team * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include #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; } cgit-1.1/configfile.h000066400000000000000000000003571301521500400145530ustar00rootroot00000000000000#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 */ cgit-1.1/contrib/000077500000000000000000000000001301521500400137305ustar00rootroot00000000000000cgit-1.1/contrib/hooks/000077500000000000000000000000001301521500400150535ustar00rootroot00000000000000cgit-1.1/contrib/hooks/post-receive.agefile000077500000000000000000000010761301521500400210050ustar00rootroot00000000000000#!/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" cgit-1.1/favicon.ico000066400000000000000000000020661301521500400144150ustar00rootroot00000000000000 &(( @PP+,fe<=94DDV9DDDDDDDDDDDDDIDOfftD4DzDJD1DC$DDEDH;DBDDDDDMDBDDDDDDDDBDDrDDBDTDBtIDBEDBDLDBDNDBDJDJDJDJDJDJDJDFDHDCDHBDK$DaDN4DHff5D!DDDDDDDIDDDDDD2tDDV( UU#$pp=>GICA[%>'{^!qNa3na~aޱaqqqq!a^!'{I(cgit-1.1/filter.c000066400000000000000000000261051301521500400137250ustar00rootroot00000000000000/* filter.c: filter framework functions * * Copyright (C) 2006-2014 cgit Development Team * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "html.h" #ifndef NO_LUA #include #include #include #include #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 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(filter->pipe_fh), "Unable to create pipe to subprocess"); filter->pid = chk_non_negative(fork(), "Unable to create subprocess"); if (filter->pid == 0) { close(filter->pipe_fh[1]); chk_non_negative(dup2(filter->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(filter->pipe_fh[0]); chk_non_negative(dup2(filter->pipe_fh[1], STDOUT_FILENO), "Unable to use pipe as STDOUT"); close(filter->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); } cgit-1.1/filters/000077500000000000000000000000001301521500400137405ustar00rootroot00000000000000cgit-1.1/filters/about-formatting.sh000077500000000000000000000021041301521500400175560ustar00rootroot00000000000000#!/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 cgit-1.1/filters/commit-links.sh000077500000000000000000000017571301521500400167170ustar00rootroot00000000000000#!/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|\1|g' # This expression generates links to a fictional bugtracker. regex=$regex' s|#([0-9]+)\b|#\1|g' sed -re "$regex" cgit-1.1/filters/email-gravatar.lua000066400000000000000000000013001301521500400173310ustar00rootroot00000000000000-- 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: -- luacrypto >= 0.3 -- -- local crypto = require("crypto") function filter_open(email, page) buffer = "" md5 = crypto.digest("md5", email:sub(2, -2):lower()) end function filter_close() html("Gravatar " .. buffer) return 0 end function filter_write(str) buffer = buffer .. str end cgit-1.1/filters/email-gravatar.py000077500000000000000000000025451301521500400172170ustar00rootroot00000000000000#!/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("Gravatar " + text) cgit-1.1/filters/email-libravatar.lua000066400000000000000000000013551301521500400176630ustar00rootroot00000000000000-- 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: -- luacrypto >= 0.3 -- -- local crypto = require("crypto") function filter_open(email, page) buffer = "" md5 = crypto.digest("md5", email:sub(2, -2):lower()) end function filter_close() baseurl = os.getenv("HTTPS") and "https://seccdn.libravatar.org/" or "http://cdn.libravatar.org/" html("Libravatar " .. buffer) return 0 end function filter_write(str) buffer = buffer .. str end cgit-1.1/filters/gentoo-ldap-authentication.lua000066400000000000000000000165241301521500400217010ustar00rootroot00000000000000-- This script may be used with the auth-filter. Be sure to configure it as you wish. -- -- Requirements: -- luacrypto >= 0.3 -- -- lualdap >= 1.2 -- -- -- -- -- 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" } -- All cookies will be authenticated based on this secret. Make it something -- totally random and impossible to guess. It should be large. local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM" -- -- -- 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("

Gentoo LDAP Authentication Required

") html("
") html("") html("") html("") html("") html("") html("
") return 0 end -- -- -- Gentoo LDAP support. -- -- local lualdap = require("lualdap") function gentoo_ldap_user_groups(username, password) -- Ensure the user is alphanumeric if 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 -- -- -- Cookie construction and validation helpers. -- -- local crypto = require("crypto") -- 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 hmac = "" 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 hmac = component else break end i = i + 1 end if hmac == nil or hmac:len() == 0 then return nil end -- Lua hashes strings, so these comparisons are time invariant. if hmac ~= crypto.hmac.digest("sha1", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, secret) 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 = crypto.hex(crypto.rand.bytes(16)) value = url_encode(value) field = url_encode(field) authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt authstr = authstr .. "|" .. crypto.hmac.digest("sha1", authstr, secret) 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 cgit-1.1/filters/html-converters/000077500000000000000000000000001301521500400170745ustar00rootroot00000000000000cgit-1.1/filters/html-converters/man2html000077500000000000000000000003301301521500400205400ustar00rootroot00000000000000#!/bin/sh echo "
" groff -mandoc -T html -P -r -P -l | egrep -v '(||||||||" cgit-1.1/filters/html-converters/md2html000077500000000000000000000176111301521500400203770ustar00rootroot00000000000000#!/usr/bin/env python3 import markdown import sys import io from pygments.formatters import HtmlFormatter sys.stdin = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8') sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') sys.stdout.write(''' ''') sys.stdout.write("
") 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"], extension_configs={"markdown.extensions.codehilite":{"css_class":"highlight"}}) sys.stdout.write("
") cgit-1.1/filters/html-converters/rst2html000077500000000000000000000001611301521500400205770ustar00rootroot00000000000000#!/bin/bash exec rst2html.py --template <(echo -e "%(stylesheet)s\n%(body_pre_docinfo)s\n%(docinfo)s\n%(body)s") cgit-1.1/filters/html-converters/txt2html000077500000000000000000000001621301521500400206070ustar00rootroot00000000000000#!/bin/sh echo "
"
sed "s|&|\\&|g;s|'|\\'|g;s|\"|\\"|g;s|<|\\<|g;s|>|\\>|g"
echo "
" cgit-1.1/filters/owner-example.lua000066400000000000000000000007271301521500400172340ustar00rootroot00000000000000-- 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("%s", "http://wiki.example.com/about/" .. buffer, buffer)) return 0 end function filter_write(str) buffer = buffer .. str end cgit-1.1/filters/simple-authentication.lua000066400000000000000000000154121301521500400207540ustar00rootroot00000000000000-- This script may be used with the auth-filter. Be sure to configure it as you wish. -- -- Requirements: -- luacrypto >= 0.3 -- -- -- -- -- 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 } } -- Please note that, in production, you'll want to replace this simple lookup -- table with either a table of salted and hashed passwords (using something -- smart like scrypt), or replace this table lookup with an external support, -- such as consulting your system's pam / shadow system, or an external -- database, or an external validating web service. For testing, or for -- extremely low-security usage, you may be able, however, to get away with -- compromising on hardcoding the passwords in cleartext, as we have done here. local users = { jason = "secretpassword", laurent = "s3cr3t", bob = "ilikelua" } -- All cookies will be authenticated based on this secret. Make it something -- totally random and impossible to guess. It should be large. local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM" -- -- -- 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 password = users[post["username"]] local redirect = validate_value("redirect", post["redirect"]) if redirect == nil then not_found() return 0 end redirect_to(redirect) -- Lua hashes strings, so these comparisons are time invariant. if password == nil or password ~= post["password"] 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("

Authentication Required

") html("
") html("") html("") html("") html("") html("") html("
") 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 -- -- -- Cookie construction and validation helpers. -- -- local crypto = require("crypto") -- 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 hmac = "" 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 hmac = component else break end i = i + 1 end if hmac == nil or hmac:len() == 0 then return nil end -- Lua hashes strings, so these comparisons are time invariant. if hmac ~= crypto.hmac.digest("sha1", field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt, secret) 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 = crypto.hex(crypto.rand.bytes(16)) value = url_encode(value) field = url_encode(field) authstr = field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt authstr = authstr .. "|" .. crypto.hmac.digest("sha1", authstr, secret) 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 cgit-1.1/filters/syntax-highlighting.py000077500000000000000000000032601301521500400203070ustar00rootroot00000000000000#!/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') sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') data = sys.stdin.read() filename = sys.argv[1] formatter = HtmlFormatter(style='pastie') 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('') sys.stdout.write(highlight(data, lexer, formatter, outfile=None)) cgit-1.1/filters/syntax-highlighting.sh000077500000000000000000000106601301521500400202730ustar00rootroot00000000000000#!/bin/sh # This script can be used to implement syntax highlighting in the cgit # tree-view by refering 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 cgit-1.1/gen-version.sh000077500000000000000000000006621301521500400150670ustar00rootroot00000000000000#!/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 cgit-1.1/git/000077500000000000000000000000001301521500400130535ustar00rootroot00000000000000cgit-1.1/html.c000066400000000000000000000166331301521500400134110ustar00rootroot00000000000000/* html.c: helper functions for html output * * Copyright (C) 2006-2014 cgit Development Team * * 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) { const char *t = txt; while (t && *t) { 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(txt); } void html_ntxt(int len, const char *txt) { const char *t = txt; while (t && *t && len--) { 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); if (len < 0) html("..."); } 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(""); } void html_option(const char *value, const char *text, const char *selected_value) { html("\n"); } void html_intoption(int value, const char *text, int selected_value) { htmlf(""); } void html_link_open(const char *url, const char *title, const char *class) { html(""); } void html_link_close(void) { html(""); } 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); } } cgit-1.1/html.h000066400000000000000000000024511301521500400134070ustar00rootroot00000000000000#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 void html_ntxt(int len, const char *txt); 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 */ cgit-1.1/parsing.c000066400000000000000000000112151301521500400140770ustar00rootroot00000000000000/* parsing.c: parsing of config files * * Copyright (C) 2006-2014 cgit Development Team * * 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; ctx.repo = NULL; if (!url || url[0] == '\0') return; 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); return; } } static char *substr(const char *head, const char *tail) { char *buf; if (tail < head) return xstrdup(""); buf = xmalloc(tail - head + 1); strncpy(buf, head, tail - head); buf[tail - head] = '\0'; 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); sprintf(*email, "<%.*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(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_sha1_file(tag->object.oid.hash, &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; } cgit-1.1/robots.txt000066400000000000000000000000571301521500400143430ustar00rootroot00000000000000User-agent: * Disallow: /*/snapshot/* Allow: / cgit-1.1/scan-tree.c000066400000000000000000000145011301521500400143160ustar00rootroot00000000000000/* scan-tree.c * * Copyright (C) 2006-2014 cgit Development Team * * 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" /* 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 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, &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); } cgit-1.1/scan-tree.h000066400000000000000000000002261301521500400143220ustar00rootroot00000000000000extern void scan_projects(const char *path, const char *projectsfile, repo_config_fn fn); extern void scan_tree(const char *path, repo_config_fn fn); cgit-1.1/shared.c000066400000000000000000000314131301521500400137040ustar00rootroot00000000000000/* shared.c: global vars + some callback functions * * Copyright (C) 2006-2014 cgit Development Team * * 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->owner = NULL; ret->homepage = NULL; ret->section = ctx.cfg.section; ret->snapshots = ctx.cfg.snapshots; 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(oid->hash); 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_sha1_file(oid->hash, &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.outf = 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; DIFF_OPT_SET(&opt, RECURSIVE); if (ignorews) DIFF_XDL_SET(&opt, IGNORE_WHITESPACE); opt.format_callback = cgit_diff_tree_cb; opt.format_callback_data = fn; if (prefix) { item.match = 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_sha1(old_oid->hash, new_oid->hash, "", &opt); else diff_root_tree_sha1(new_oid->hash, "", &opt); diffcore_std(&opt); diff_flush(&opt); } 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; 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 |= f->bit; 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; int len; len = 0; value = getenv(name); if (value) { len = strlen(value); if (len > maxlength) len = maxlength; strncpy(name, value, 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; } cgit-1.1/tests/000077500000000000000000000000001301521500400134325ustar00rootroot00000000000000cgit-1.1/tests/.gitignore000066400000000000000000000000411301521500400154150ustar00rootroot00000000000000trash\ directory.t* test-results cgit-1.1/tests/Makefile000066400000000000000000000004211301521500400150670ustar00rootroot00000000000000include ../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 cgit-1.1/tests/filters/000077500000000000000000000000001301521500400151025ustar00rootroot00000000000000cgit-1.1/tests/filters/dump.lua000066400000000000000000000003701301521500400165520ustar00rootroot00000000000000function 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 cgit-1.1/tests/filters/dump.sh000077500000000000000000000001121301521500400164000ustar00rootroot00000000000000#!/bin/sh [ "$#" -gt 0 ] && printf "%s " "$*" tr '[:lower:]' '[:upper:]' cgit-1.1/tests/setup.sh000077500000000000000000000073531301521500400151410ustar00rootroot00000000000000# 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 <>cgitrc <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//" >sm_version && test_cmp sm_version makefile_version fi ' test_done cgit-1.1/tests/t0010-validate-html.sh000077500000000000000000000016451301521500400172740ustar00rootroot00000000000000#!/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 cgit-1.1/tests/t0020-validate-cache.sh000077500000000000000000000034341301521500400173720ustar00rootroot00000000000000#!/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 && 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 cgit-1.1/tests/t0101-index.sh000077500000000000000000000013131301521500400156410ustar00rootroot00000000000000#!/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 cgit-1.1/tests/t0102-summary.sh000077500000000000000000000016301301521500400162320ustar00rootroot00000000000000#!/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 cgit-1.1/tests/t0103-log.sh000077500000000000000000000020131301521500400153130ustar00rootroot00000000000000#!/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 cgit-1.1/tests/t0104-tree.sh000077500000000000000000000014771301521500400155070ustar00rootroot00000000000000#!/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 "1" tmp ' test_expect_success 'no line 2' ' ! grep "2" 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 cgit-1.1/tests/t0105-commit.sh000077500000000000000000000021341301521500400160300ustar00rootroot00000000000000#!/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 "" tmp' test_expect_success 'find parent link' 'grep -E "" tmp' test_expect_success 'find commit subject' ' grep "
commit 5<" tmp ' test_expect_success 'find commit msg' 'grep "
" tmp' test_expect_success 'find diffstat' 'grep "" 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 "" tmp ' test_expect_success 'root commit contains diffstat' ' grep "file-1" tmp ' test_expect_success 'root commit contains diff' ' grep ">diff --git a/file-1 b/file-1<" tmp && grep "
+1
" tmp ' test_done cgit-1.1/tests/t0106-diff.sh000077500000000000000000000010421301521500400154460ustar00rootroot00000000000000#!/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 "@@ -0,0 +1 @@" tmp ' test_expect_success 'find added line' ' grep "
+5
" tmp ' test_done cgit-1.1/tests/t0107-snapshot.sh000077500000000000000000000032201301521500400163760ustar00rootroot00000000000000#!/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 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 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 cgit-1.1/tests/t0108-patch.sh000077500000000000000000000032751301521500400156510ustar00rootroot00000000000000#!/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_ && 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_ && test_cmp tmp_ tmp2 ' test_done cgit-1.1/tests/t0109-gitconfig.sh000077500000000000000000000021301301521500400165110ustar00rootroot00000000000000#!/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_existant_path="/path/to/some/place/that/does/not/possibly/exist" while test -d "$non_existant_path"; do non_existant_path="$non_existant_path/$(date +%N)" done && strace \ -E HOME="$non_existant_path" \ -E CGIT_CONFIG="$PWD/cgitrc" \ -E QUERY_STRING="url=$1" \ -e access -f -o strace.out cgit && test_must_fail grep "$non_existant_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 cgit-1.1/tests/t0110-rawdiff.sh000077500000000000000000000022021301521500400161520ustar00rootroot00000000000000#!/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 cgit-1.1/tests/t0111-filter.sh000077500000000000000000000022541301521500400160250ustar00rootroot00000000000000#!/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 "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 "
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 "
ADD A+B" tmp ' test_expect_success "check whether the $prefix email filter works for authors" ' grep " commit A U THOR <AUTHOR@EXAMPLE.COM>" tmp ' test_expect_success "check whether the $prefix email filter works for committers" ' grep " commit C O MITTER <COMMITTER@EXAMPLE.COM>" tmp ' done test_done cgit-1.1/tests/valgrind/000077500000000000000000000000001301521500400152405ustar00rootroot00000000000000cgit-1.1/tests/valgrind/bin/000077500000000000000000000000001301521500400160105ustar00rootroot00000000000000cgit-1.1/tests/valgrind/bin/cgit000077500000000000000000000005661301521500400166730ustar00rootroot00000000000000#!/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" "$@" cgit-1.1/ui-atom.c000066400000000000000000000065471301521500400140230ustar00rootroot00000000000000/* ui-atom.c: functions for atom feeds * * Copyright (C) 2006-2014 cgit Development Team * * 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("\n"); html(""); html_txt(info->subject); html("\n"); html(""); html_txt(show_date(info->committer_date, 0, date_mode_from_type(DATE_ISO8601_STRICT))); html("\n"); html("\n"); if (info->author) { html(""); html_txt(info->author); html("\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(""); html_txt(t); html("\n"); free(mail); } html("\n"); html(""); html_txt(show_date(info->author_date, 0, date_mode_from_type(DATE_ISO8601_STRICT))); html("\n"); if (host) { char *pageurl; html("\n"); free(pageurl); } htmlf("%s\n", hex); html("\n"); html_txt(info->msg); html("\n"); html("\n"); html("
\n"); html("
\n");
	html_txt(info->msg);
	html("
\n"); html("
\n"); html("
\n"); html("
\n"); cgit_free_commitinfo(info); } void cgit_print_atom(char *tip, 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("\n"); html(""); html_txt(ctx.repo->name); if (path) { html("/"); html_txt(path); } if (tip && !ctx.qry.show_all) { html(", branch "); html_txt(tip); } html("\n"); html(""); html_txt(ctx.repo->desc); html("\n"); if (host) { char *repourl = cgit_repourl(ctx.repo->url); html("\n"); free(repourl); } while ((commit = get_revision(&rev)) != NULL) { add_entry(commit, host); free_commit_buffer(commit); free_commit_list(commit->parents); commit->parents = NULL; } html("\n"); free(host); } cgit-1.1/ui-atom.h000066400000000000000000000001601301521500400140110ustar00rootroot00000000000000#ifndef UI_ATOM_H #define UI_ATOM_H extern void cgit_print_atom(char *tip, char *path, int max_count); #endif cgit-1.1/ui-blob.c000066400000000000000000000101251301521500400137640ustar00rootroot00000000000000/* ui-blob.c: show blob content * * Copyright (C) 2006-2014 cgit Development Team * * 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 unsigned char *sha1, 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; hashcpy(walk_tree_ctx->matched_oid->hash, sha1); 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 = 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)) return 0; if (sha1_object_info(oid.hash, &size) != OBJ_COMMIT) return 0; read_tree_recursive(lookup_commit_reference(oid.hash)->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); 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 = sha1_object_info(oid.hash, &size); if (type == OBJ_COMMIT) { commit = lookup_commit_reference(oid.hash); read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); if (!walk_tree_ctx.found_path) return -1; type = sha1_object_info(oid.hash, &size); } if (type == OBJ_BAD) return -1; buf = read_sha1_file(oid.hash, &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 = sha1_object_info(oid.hash, &size); if ((!hex) && type == OBJ_COMMIT && path) { commit = lookup_commit_reference(oid.hash); read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); type = sha1_object_info(oid.hash, &size); } if (type == OBJ_BAD) { cgit_print_error_page(404, "Not found", "Bad object name: %s", hex); return; } buf = read_sha1_file(oid.hash, &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); } cgit-1.1/ui-blob.h000066400000000000000000000004641301521500400137760ustar00rootroot00000000000000#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 */ cgit-1.1/ui-clone.c000066400000000000000000000045761301521500400141630ustar00rootroot00000000000000/* 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 * * 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" 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(oid->hash))) return 0; htmlf("%s\t%s\n", oid_to_hex(oid), refname); if (obj->type == OBJ_TAG) { if (!(obj = deref_tag(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(); prepare_packed_git(); for (pack = packed_git; 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) { if (!ctx.qry.path) { cgit_print_error_page(400, "Bad request", "Bad request"); return; } if (!strcmp(ctx.qry.path, "info/packs")) { print_pack_info(); return; } send_file(git_path("objects/%s", ctx.qry.path)); } void cgit_clone_head(void) { send_file(git_path("%s", "HEAD")); } cgit-1.1/ui-clone.h000066400000000000000000000002271301521500400141550ustar00rootroot00000000000000#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 */ cgit-1.1/ui-commit.c000066400000000000000000000104311301521500400143360ustar00rootroot00000000000000/* ui-commit.c: generate commit view * * Copyright (C) 2006-2014 cgit Development Team * * 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(oid.hash); if (!commit) { cgit_print_error_page(404, "Not found", "Bad commit reference: %s", hex); return; } info = cgit_parse_commit(commit); format_display_notes(oid.hash, ¬es, PAGE_ENCODING, 0); load_ref_decorations(DECORATE_FULL_REFS); cgit_print_layout_start(); cgit_print_diff_ctrls(); html("
\n"); html("\n"); html("\n"); html("\n"); html("\n"); for (p = commit->parents; p; p = p->next) { parent = lookup_commit_reference(p->item->object.oid.hash); if (!parent) { html(""); continue; } html("" ""); parents++; } if (ctx.repo->snapshots) { html(""); } html("
author"); 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(""); html_txt(show_date(info->author_date, info->author_tz, cgit_date_mode(DATE_ISO8601))); html("
committer"); 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(""); html_txt(show_date(info->committer_date, info->committer_tz, cgit_date_mode(DATE_ISO8601))); html("
commit"); 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(")
tree"); tmp = xstrdup(hex); cgit_tree_link(oid_to_hex(&commit->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("
"); cgit_print_error("Error reading parent commit"); html("
parent"); 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(")
download"); cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, hex, ctx.repo->snapshots); html("
\n"); html("
"); cgit_open_filter(ctx.repo->commit_filter); html_txt(info->subject); cgit_close_filter(ctx.repo->commit_filter); show_commit_decorations(commit); html("
"); html("
"); cgit_open_filter(ctx.repo->commit_filter); html_txt(info->msg); cgit_close_filter(ctx.repo->commit_filter); html("
"); if (notes.len != 0) { html("
Notes
"); html("
"); cgit_open_filter(ctx.repo->commit_filter); html_txt(notes.buf); cgit_close_filter(ctx.repo->commit_filter); html("
"); html(""); } 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(); } cgit-1.1/ui-commit.h000066400000000000000000000002011301521500400143350ustar00rootroot00000000000000#ifndef UI_COMMIT_H #define UI_COMMIT_H extern void cgit_print_commit(char *hex, const char *prefix); #endif /* UI_COMMIT_H */ cgit-1.1/ui-diff.c000066400000000000000000000315151301521500400137640ustar00rootroot00000000000000/* ui-diff.c: show diff between two blobs * * Copyright (C) 2006-2014 cgit Development Team * * 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(""); htmlf(""); 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("["); cgit_print_filemode(info->old_mode); html("]"); } htmlf("", 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(""); if (info->binary) { htmlf("bin%ld -> %ld bytes", info->old_size, info->new_size); return; } htmlf("%d", info->added + info->removed); html(""); htmlf("", (max_changes > 100 ? 100 : max_changes)); htmlf("
", info->added * 100.0 / max_changes); htmlf("", info->removed * 100.0 / max_changes); htmlf("", (max_changes - info->removed - info->added) * 100.0 / max_changes); html("
\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("
"); 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("
"); html(""); max_changes = 0; cgit_diff_tree(old_oid, new_oid, inspect_filepair, prefix, ctx.qry.ignorews); for (i = 0; i"); html("
"); htmlf("%d files changed, %d insertions, %d deletions", files, total_adds, total_rems); html("
"); } /* * 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("
", class); line[len-1] = '\0'; html_txt(line); html("
"); 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("
"); html("diff --git a/"); html_txt(path1); html(" b/"); html_txt(path2); if (mode1 == 0) htmlf("
new file mode %.6o", mode2); if (mode2 == 0) htmlf("
deleted file mode %.6o", mode1); if (!subproject) { abbrev1 = xstrdup(find_unique_abbrev(oid1->hash, DEFAULT_ABBREV)); abbrev2 = xstrdup(find_unique_abbrev(oid2->hash, DEFAULT_ABBREV)); htmlf("
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("
--- /"); } else html("
--- 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("
+++ /"); } else html("
+++ 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("
"); } 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("
"); else html("Binary files differ"); } if (use_ssdiff) cgit_ssdiff_footer(); } void cgit_print_diff_ctrls(void) { int i, curr; html("
"); html("diff options"); html("
"); cgit_add_hidden_formfields(1, 0, ctx.qry.page); html("
Binary files differ
"); html(""); html(""); html(""); html(""); html(""); html(""); html(""); html(""); html(""); html(""); html("
context:"); html(""); html("
space:"); html(""); html("
mode:"); html("
"); html(""); html("
"); html(""); html("
"); } 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 unsigned char *old_tree_sha1, *new_tree_sha1; 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(new_rev_oid->hash); if (!commit || parse_commit(commit)) { cgit_print_error_page(404, "Not found", "Bad commit: %s", oid_to_hex(new_rev_oid)); return; } new_tree_sha1 = commit->tree->object.oid.hash; 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(old_rev_oid->hash); if (!commit2 || parse_commit(commit2)) { cgit_print_error_page(404, "Not found", "Bad commit: %s", oid_to_hex(old_rev_oid)); return; } old_tree_sha1 = commit2->tree->object.oid.hash; } else { old_tree_sha1 = NULL; } if (raw) { struct diff_options diffopt; diff_setup(&diffopt); diffopt.output_format = DIFF_FORMAT_PATCH; DIFF_OPT_SET(&diffopt, RECURSIVE); diff_setup_done(&diffopt); ctx.page.mimetype = "text/plain"; cgit_print_http_headers(); if (old_tree_sha1) { diff_tree_sha1(old_tree_sha1, new_tree_sha1, "", &diffopt); } else { diff_root_tree_sha1(new_tree_sha1, "", &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(""); } else { html("
"); html(""); html("
"); } cgit_diff_tree(old_rev_oid, new_rev_oid, filepair_cb, prefix, ctx.qry.ignorews); if (!use_ssdiff) html("
"); if (show_ctrls) cgit_print_layout_end(); } cgit-1.1/ui-diff.h000066400000000000000000000006601301521500400137660ustar00rootroot00000000000000#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 */ cgit-1.1/ui-log.c000066400000000000000000000344111301521500400136330ustar00rootroot00000000000000/* ui-log.c: functions for log output * * Copyright (C) 2006-2014 cgit Development Team * * 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[] = { "", "", "", "", "", "", "", }; #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(""); while (deco) { strncpy(buf, prettify_refname(deco->name), sizeof(buf) - 1); 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: cgit_tag_link(buf, NULL, "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(""); } 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; unsigned 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 -- ". */ 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; DIFF_OPT_SET(&revs->diffopt, RECURSIVE); diff_tree_sha1(parent->tree->object.oid.hash, commit->tree->object.oid.hash, "", &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(""); html(graphbuf.buf); htmlf("\n", columns); strbuf_setlen(&graphbuf, 0); } /* Current commit's graph segment is now ready in graphbuf */ } info = cgit_parse_commit(commit); htmlf("", ctx.qry.showmsg ? " class='logheader'" : ""); if (revs->graph) { /* Print graph segment for current commit */ html(""); html(graphbuf.buf); html(""); strbuf_setlen(&graphbuf, 0); } else { html(""); cgit_print_age(info->committer_date, info->committer_tz, TM_WEEK * 2); html(""); } htmlf("", 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 */ strcpy(info->subject + i, wrap_symbol); } } cgit_commit_link(info->subject, NULL, NULL, ctx.qry.head, oid_to_hex(&commit->object.oid), ctx.qry.vpath); show_commit_decorations(commit); html(""); 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(""); 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("%d", files); if (ctx.repo->enable_log_linecount) htmlf("-%d/" "+%d", rem_lines, add_lines); html("\n"); if ((revs->graph && !graph_is_commit_finished(revs->graph)) || ctx.qry.showmsg) { /* Print a second table row */ html(""); 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.hash, &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(""); 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("\n"); } else html(""); /* Empty 'Age' column */ /* Print msgbuf into remainder of table row */ htmlf("\n", columns - (revs->graph ? 1 : 0), ctx.qry.showmsg ? " class='logmsg'" : ""); html_txt(msgbuf.buf); html("\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, 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(DECORATE_FULL_REFS); rev.show_decorations = 1; rev.grep_filter.regflags |= REG_ICASE; 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(""); } html(""); if (commit_graph) html(""); else html(""); html(""); if (rev.graph) html(""); if (ctx.repo->enable_log_filecount) { html(""); columns++; } if (ctx.repo->enable_log_linecount) { html(""); columns++; } html("\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(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(commit); free_commit_list(commit->parents); commit->parents = NULL; } if (pager) { html("
AgeCommit 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("AuthorAgeFilesLines
    "); if (ofs > 0) { html("
  • "); 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("
  • "); } if ((commit = get_revision(&rev)) != NULL) { html("
  • "); 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("
  • "); } html("
"); cgit_print_layout_end(); } else if ((commit = get_revision(&rev)) != NULL) { htmlf("", columns); cgit_log_link("[...]", NULL, NULL, ctx.qry.head, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, ctx.qry.follow); html("\n"); } /* If we allocated tip then it is safe to cast away const. */ if (must_free_tip) free((char*) tip); } cgit-1.1/ui-log.h000066400000000000000000000004261301521500400136370ustar00rootroot00000000000000#ifndef UI_LOG_H #define UI_LOG_H extern void cgit_print_log(const char *tip, int ofs, int cnt, char *grep, char *pattern, char *path, int pager, int commit_graph, int commit_sort); extern void show_commit_decorations(struct commit *commit); #endif /* UI_LOG_H */ cgit-1.1/ui-patch.c000066400000000000000000000047771301521500400141650ustar00rootroot00000000000000/* ui-patch.c: generate patch view * * Copyright (C) 2006-2014 cgit Development Team * * 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" 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[2 * 40 + 3]; const char *rev_argv[] = { NULL, "--reverse", "--format=email", rev_range, "--", prefix }; int rev_argc = ARRAY_SIZE(rev_argv); 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(new_rev_oid.hash); 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(old_rev_oid.hash)) { 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 { sprintf(rev_range, "%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(ARRAY_SIZE(rev_argv), 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); } fflush(stdout); } cgit-1.1/ui-patch.h000066400000000000000000000002441301521500400141530ustar00rootroot00000000000000#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 */ cgit-1.1/ui-plain.c000066400000000000000000000120021301521500400141450ustar00rootroot00000000000000/* ui-plain.c: functions for output of plain blobs by path * * Copyright (C) 2006-2014 cgit Development Team * * 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 unsigned char *sha1, const char *path) { enum object_type type; char *buf, *mimetype; unsigned long size; type = sha1_object_info(sha1, &size); if (type == OBJ_BAD) { cgit_print_error_page(404, "Not found", "Not found"); return 0; } buf = read_sha1_file(sha1, &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 = sha1_to_hex(sha1); 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 unsigned char *sha1, 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 = sha1_to_hex(sha1); cgit_print_http_headers(); htmlf("%s", slash); html_txt(fullpath); htmlf("\n\n

%s", slash); html_txt(fullpath); html("

\n
    \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("
  • "); cgit_plain_link("../", NULL, NULL, ctx.qry.head, ctx.qry.sha1, fullpath); html("
  • \n"); } free(fullpath); } static void print_dir_entry(const unsigned char *sha1, 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("
  • "); if (S_ISGITLINK(mode)) { cgit_submodule_link(NULL, fullpath, sha1_to_hex(sha1)); } else cgit_plain_link(path, NULL, NULL, ctx.qry.head, ctx.qry.sha1, fullpath); html("
  • \n"); free(fullpath); } static void print_dir_tail(void) { html("
\n\n"); } static int walk_tree(const unsigned char *sha1, 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)) { if (print_object(sha1, pathname)) walk_tree_ctx->match = 1; } else if (S_ISDIR(mode)) { print_dir(sha1, 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(sha1, 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(oid.hash); 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->tree->object.oid.hash, "", 0, ""); walk_tree_ctx.match = 2; } else walk_tree_ctx.match_baselen = basedir_len(path_items.match); read_tree_recursive(commit->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(); } cgit-1.1/ui-plain.h000066400000000000000000000001441301521500400141560ustar00rootroot00000000000000#ifndef UI_PLAIN_H #define UI_PLAIN_H extern void cgit_print_plain(void); #endif /* UI_PLAIN_H */ cgit-1.1/ui-refs.c000066400000000000000000000140471301521500400140140ustar00rootroot00000000000000/* ui-refs.c: browse symbolic refs * * Copyright (C) 2006-2014 cgit Development Team * * 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(""); cgit_log_link(name, NULL, NULL, name, NULL, NULL, 0, NULL, NULL, ctx.qry.showmsg, 0); html(""); if (ref->object->type == OBJ_COMMIT) { cgit_commit_link(info->subject, NULL, NULL, name, NULL, NULL); html(""); cgit_open_filter(ctx.repo->email_filter, info->author_email, "refs"); html_txt(info->author); cgit_close_filter(ctx.repo->email_filter); html(""); cgit_print_age(info->committer_date, info->committer_tz, -1); } else { html(""); cgit_object_link(ref->object); } html("\n"); return 0; } static void print_tag_header(void) { html("Tag" "Download" "Author" "Age\n"); } static void print_tag_downloads(const struct cgit_repo *repo, const char *ref) { const struct cgit_snapshot_format* f; const char *basename; struct strbuf filename = STRBUF_INIT; size_t prefixlen; if (!ref || strlen(ref) < 1) return; basename = cgit_repobasename(repo->url); if (starts_with(ref, basename)) strbuf_addstr(&filename, ref); else cgit_compose_snapshot_prefix(&filename, basename, ref); prefixlen = filename.len; for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(repo->snapshots & f->bit)) continue; strbuf_setlen(&filename, prefixlen); strbuf_addstr(&filename, f->suffix); cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL, filename.buf); html("  "); } strbuf_release(&filename); } 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(""); cgit_tag_link(name, NULL, NULL, name); html(""); if (ctx.repo->snapshots && (obj->type == OBJ_COMMIT)) print_tag_downloads(ctx.repo, name); else cgit_object_link(obj); html(""); 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(""); 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("\n"); return 0; } static void print_refs_link(char *path) { html(""); cgit_refs_link("[...]", NULL, NULL, ctx.qry.head, NULL, path); html(""); } void cgit_print_branches(int maxcount) { struct reflist list; int i; html("Branch" "Commit message" "Author" "Age\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(""); 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(""); cgit_print_tags(0); } html("
 
"); cgit_print_layout_end(); } cgit-1.1/ui-refs.h000066400000000000000000000002721301521500400140140ustar00rootroot00000000000000#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 */ cgit-1.1/ui-repolist.c000066400000000000000000000211371301521500400147140ustar00rootroot00000000000000/* ui-repolist.c: functions for generating the repolist page * * Copyright (C) 2006-2014 cgit Development Team * * 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(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("
%s", title); free(currenturl); } static void print_header(void) { html(""); 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("Links"); html("\n"); } static void print_pager(int items, int pagelen, char *search, char *sort) { int i, ofs; char *class = NULL; html("
    "); for (i = 0, ofs = 0; ofs < items; i++, ofs = i * pagelen) { class = (ctx.qry.ofs == ofs) ? "current" : NULL; html("
  • "); cgit_index_link(fmt("[%d]", i + 1), fmt("Page %d", i + 1), class, search, sort, ofs, 0); html("
  • "); } html("
"); } 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_section(const void *a, const void *b) { const struct cgit_repo *r1 = a; const struct cgit_repo *r2 = b; int result; time_t t; result = cmp(r1->section, r2->section); if (!result) { if (!strcmp(ctx.cfg.repository_sort, "age")) { // get_repo_modtime caches the value in r->mtime, so we don't // have to worry about inefficiencies here. if (get_repo_modtime(r1, &t) && get_repo_modtime(r2, &t)) result = r2->mtime - r1->mtime; } if (!result) result = cmp(r1->name, r2->name); } return result; } 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; } 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.cfg.index_header) html_include(ctx.cfg.index_header); if (ctx.qry.sort) sorted = sort_repolist(ctx.qry.sort); else if (ctx.cfg.section_sort) sort_repolist("section"); html(""); 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(""); last_section = section; } htmlf(""); if (ctx.cfg.enable_index_links) { html(""); } html("\n"); } html("
", columns); html_txt(section); html("
", !sorted && section ? "sublevel-repo" : "toplevel-repo"); cgit_summary_link(ctx.repo->name, ctx.repo->name, NULL, NULL); html(""); repourl = cgit_repourl(ctx.repo->url); html_link_open(repourl, NULL, NULL); free(repourl); html_ntxt(ctx.cfg.max_repodesc_len, ctx.repo->desc); html_link_close(); html(""); 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(""); html_txt(ctx.repo->owner); html(""); free(currenturl); } html(""); } print_modtime(ctx.repo); html(""); 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("
"); 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(); } cgit-1.1/ui-repolist.h000066400000000000000000000002321301521500400147120ustar00rootroot00000000000000#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 */ cgit-1.1/ui-shared.c000066400000000000000000000667451301521500400143370ustar00rootroot00000000000000/* ui-shared.c: common web output functions * * Copyright (C) 2006-2014 cgit Development Team * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "ui-shared.h" #include "cmd.h" #include "html.h" static const char cgit_doctype[] = "\n"; static char *http_date(time_t t) { static char day[][4] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static char month[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; struct tm *tm = gmtime(&t); return fmt("%s, %02d %s %04d %02d:%02d:%02d GMT", day[tm->tm_wday], tm->tm_mday, month[tm->tm_mon], 1900 + tm->tm_year, tm->tm_hour, tm->tm_min, tm->tm_sec); } void cgit_print_error(const char *fmt, ...) { va_list ap; va_start(ap, fmt); cgit_vprint_error(fmt, ap); va_end(ap); } void cgit_vprint_error(const char *fmt, va_list ap) { va_list cp; html("
"); va_copy(cp, ap); html_vtxtf(fmt, cp); va_end(cp); html("
\n"); } const char *cgit_httpscheme(void) { if (ctx.env.https && !strcmp(ctx.env.https, "on")) return "https://"; else return "http://"; } char *cgit_hosturl(void) { if (ctx.env.http_host) return xstrdup(ctx.env.http_host); if (!ctx.env.server_name) return NULL; if (!ctx.env.server_port || atoi(ctx.env.server_port) == 80) return xstrdup(ctx.env.server_name); return fmtalloc("%s:%s", ctx.env.server_name, ctx.env.server_port); } char *cgit_currenturl(void) { const char *root = cgit_rooturl(); size_t len = strlen(root); if (!ctx.qry.url) return xstrdup(root); if (len && root[len - 1] == '/') return fmtalloc("%s%s", root, ctx.qry.url); return fmtalloc("%s/%s", root, ctx.qry.url); } const char *cgit_rooturl(void) { if (ctx.cfg.virtual_root) return ctx.cfg.virtual_root; else return ctx.cfg.script_name; } const char *cgit_loginurl(void) { static const char *login_url; if (!login_url) login_url = fmtalloc("%s?p=login", cgit_rooturl()); return login_url; } char *cgit_repourl(const char *reponame) { if (ctx.cfg.virtual_root) return fmtalloc("%s%s/", ctx.cfg.virtual_root, reponame); else return fmtalloc("?r=%s", reponame); } char *cgit_fileurl(const char *reponame, const char *pagename, const char *filename, const char *query) { struct strbuf sb = STRBUF_INIT; char *delim; if (ctx.cfg.virtual_root) { strbuf_addf(&sb, "%s%s/%s/%s", ctx.cfg.virtual_root, reponame, pagename, (filename ? filename:"")); delim = "?"; } else { strbuf_addf(&sb, "?url=%s/%s/%s", reponame, pagename, (filename ? filename : "")); delim = "&"; } if (query) strbuf_addf(&sb, "%s%s", delim, query); return strbuf_detach(&sb, NULL); } char *cgit_pageurl(const char *reponame, const char *pagename, const char *query) { return cgit_fileurl(reponame, pagename, NULL, query); } const char *cgit_repobasename(const char *reponame) { /* I assume we don't need to store more than one repo basename */ static char rvbuf[1024]; int p; const char *rv; strncpy(rvbuf, reponame, sizeof(rvbuf)); if (rvbuf[sizeof(rvbuf)-1]) die("cgit_repobasename: truncated repository name '%s'", reponame); p = strlen(rvbuf)-1; /* strip trailing slashes */ while (p && rvbuf[p] == '/') rvbuf[p--] = 0; /* strip trailing .git */ if (p >= 3 && starts_with(&rvbuf[p-3], ".git")) { p -= 3; rvbuf[p--] = 0; } /* strip more trailing slashes if any */ while ( p && rvbuf[p] == '/') rvbuf[p--] = 0; /* find last slash in the remaining string */ rv = strrchr(rvbuf,'/'); if (rv) return ++rv; return rvbuf; } static void site_url(const char *page, const char *search, const char *sort, int ofs, int always_root) { char *delim = "?"; if (always_root || page) html_attr(cgit_rooturl()); else { char *currenturl = cgit_currenturl(); html_attr(currenturl); free(currenturl); } if (page) { htmlf("?p=%s", page); delim = "&"; } if (search) { html(delim); html("q="); html_attr(search); delim = "&"; } if (sort) { html(delim); html("s="); html_attr(sort); delim = "&"; } if (ofs) { html(delim); htmlf("ofs=%d", ofs); } } static void site_link(const char *page, const char *name, const char *title, const char *class, const char *search, const char *sort, int ofs, int always_root) { html(""); html_txt(name); html(""); } void cgit_index_link(const char *name, const char *title, const char *class, const char *pattern, const char *sort, int ofs, int always_root) { site_link(NULL, name, title, class, pattern, sort, ofs, always_root); } static char *repolink(const char *title, const char *class, const char *page, const char *head, const char *path) { char *delim = "?"; html("defbranch && strcmp(head, ctx.repo->defbranch)) { html(delim); html("h="); html_url_arg(head); delim = "&"; } return fmt("%s", delim); } static void reporevlink(const char *page, const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { char *delim; delim = repolink(title, class, page, head, path); if (rev && ctx.qry.head != NULL && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); } html("'>"); html_txt(name); html(""); } void cgit_summary_link(const char *name, const char *title, const char *class, const char *head) { reporevlink(NULL, name, title, class, head, NULL, NULL); } void cgit_tag_link(const char *name, const char *title, const char *class, const char *tag) { reporevlink("tag", name, title, class, tag, NULL, NULL); } void cgit_tree_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("tree", name, title, class, head, rev, path); } void cgit_plain_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("plain", name, title, class, head, rev, path); } 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) { char *delim; delim = repolink(title, class, "log", head, path); if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); delim = "&"; } if (grep && pattern) { html(delim); html("qt="); html_url_arg(grep); delim = "&"; html(delim); html("q="); html_url_arg(pattern); } if (ofs > 0) { html(delim); html("ofs="); htmlf("%d", ofs); delim = "&"; } if (showmsg) { html(delim); html("showmsg=1"); delim = "&"; } if (follow) { html(delim); html("follow=1"); } html("'>"); html_txt(name); html(""); } void cgit_commit_link(char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { char *delim; if (strlen(name) > ctx.cfg.max_msg_len && ctx.cfg.max_msg_len >= 15) { name[ctx.cfg.max_msg_len] = '\0'; name[ctx.cfg.max_msg_len - 1] = '.'; name[ctx.cfg.max_msg_len - 2] = '.'; name[ctx.cfg.max_msg_len - 3] = '.'; } delim = repolink(title, class, "commit", head, path); if (rev && ctx.qry.head && strcmp(rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(rev); delim = "&"; } if (ctx.qry.difftype) { html(delim); htmlf("dt=%d", ctx.qry.difftype); delim = "&"; } if (ctx.qry.context > 0 && ctx.qry.context != 3) { html(delim); html("context="); htmlf("%d", ctx.qry.context); delim = "&"; } if (ctx.qry.ignorews) { html(delim); html("ignorews=1"); delim = "&"; } if (ctx.qry.follow) { html(delim); html("follow=1"); } html("'>"); if (name[0] != '\0') html_txt(name); else html_txt("(no commit message)"); html(""); } void cgit_refs_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("refs", name, title, class, head, rev, path); } void cgit_snapshot_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *archivename) { reporevlink("snapshot", name, title, class, head, rev, archivename); } 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) { char *delim; delim = repolink(title, class, "diff", head, path); if (new_rev && ctx.qry.head != NULL && strcmp(new_rev, ctx.qry.head)) { html(delim); html("id="); html_url_arg(new_rev); delim = "&"; } if (old_rev) { html(delim); html("id2="); html_url_arg(old_rev); delim = "&"; } if (ctx.qry.difftype) { html(delim); htmlf("dt=%d", ctx.qry.difftype); delim = "&"; } if (ctx.qry.context > 0 && ctx.qry.context != 3) { html(delim); html("context="); htmlf("%d", ctx.qry.context); delim = "&"; } if (ctx.qry.ignorews) { html(delim); html("ignorews=1"); delim = "&"; } if (ctx.qry.follow) { html(delim); html("follow=1"); } html("'>"); html_txt(name); html(""); } void cgit_patch_link(const char *name, const char *title, const char *class, const char *head, const char *rev, const char *path) { reporevlink("patch", name, title, class, head, rev, path); } void cgit_stats_link(const char *name, const char *title, const char *class, const char *head, const char *path) { reporevlink("stats", name, title, class, head, NULL, path); } static void cgit_self_link(char *name, const char *title, const char *class) { if (!strcmp(ctx.qry.page, "repolist")) cgit_index_link(name, title, class, ctx.qry.search, ctx.qry.sort, ctx.qry.ofs, 1); else if (!strcmp(ctx.qry.page, "summary")) cgit_summary_link(name, title, class, ctx.qry.head); else if (!strcmp(ctx.qry.page, "tag")) cgit_tag_link(name, title, class, ctx.qry.has_sha1 ? ctx.qry.sha1 : ctx.qry.head); else if (!strcmp(ctx.qry.page, "tree")) cgit_tree_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "plain")) cgit_plain_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "log")) cgit_log_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path, ctx.qry.ofs, ctx.qry.grep, ctx.qry.search, ctx.qry.showmsg, ctx.qry.follow); else if (!strcmp(ctx.qry.page, "commit")) cgit_commit_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "patch")) cgit_patch_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "refs")) cgit_refs_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "snapshot")) cgit_snapshot_link(name, title, class, ctx.qry.head, ctx.qry.has_sha1 ? ctx.qry.sha1 : NULL, ctx.qry.path); else if (!strcmp(ctx.qry.page, "diff")) cgit_diff_link(name, title, class, ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, ctx.qry.path); else if (!strcmp(ctx.qry.page, "stats")) cgit_stats_link(name, title, class, ctx.qry.head, ctx.qry.path); else { /* Don't known how to make link for this page */ repolink(title, class, ctx.qry.page, ctx.qry.head, ctx.qry.path); html(">"); html_txt(name); html(""); } } void cgit_object_link(struct object *obj) { char *page, *shortrev, *fullrev, *name; fullrev = oid_to_hex(&obj->oid); shortrev = xstrdup(fullrev); shortrev[10] = '\0'; if (obj->type == OBJ_COMMIT) { cgit_commit_link(fmt("commit %s...", shortrev), NULL, NULL, ctx.qry.head, fullrev, NULL); return; } else if (obj->type == OBJ_TREE) page = "tree"; else if (obj->type == OBJ_TAG) page = "tag"; else page = "blob"; name = fmt("%s %s...", typename(obj->type), shortrev); reporevlink(page, name, NULL, NULL, ctx.qry.head, fullrev, NULL); } static struct string_list_item *lookup_path(struct string_list *list, const char *path) { struct string_list_item *item; while (path && path[0]) { if ((item = string_list_lookup(list, path))) return item; if (!(path = strchr(path, '/'))) break; path++; } return NULL; } void cgit_submodule_link(const char *class, char *path, const char *rev) { struct string_list *list; struct string_list_item *item; char tail, *dir; size_t len; len = 0; tail = 0; list = &ctx.repo->submodules; item = lookup_path(list, path); if (!item) { len = strlen(path); tail = path[len - 1]; if (tail == '/') { path[len - 1] = 0; item = lookup_path(list, path); } } if (item || ctx.repo->module_link) { html("module_link, dir, rev); } html("'>"); html_txt(path); html(""); } else { html(""); html_txt(path); html(""); } html_txtf(" @ %.7s", rev); if (item && tail) path[len - 1] = tail; } const struct date_mode *cgit_date_mode(enum date_mode_type type) { static struct date_mode mode; mode.type = type; mode.local = ctx.cfg.local_time; return &mode; } static void print_rel_date(time_t t, int tz, double value, const char *class, const char *suffix) { htmlf("%.0f %s", value, suffix); } void cgit_print_age(time_t t, int tz, time_t max_relative) { time_t now, secs; if (!t) return; time(&now); secs = now - t; if (secs < 0) secs = 0; if (secs > max_relative && max_relative >= 0) { html(""); html_txt(show_date(t, tz, cgit_date_mode(DATE_SHORT))); html(""); return; } if (secs < TM_HOUR * 2) { print_rel_date(t, tz, secs * 1.0 / TM_MIN, "age-mins", "min."); return; } if (secs < TM_DAY * 2) { print_rel_date(t, tz, secs * 1.0 / TM_HOUR, "age-hours", "hours"); return; } if (secs < TM_WEEK * 2) { print_rel_date(t, tz, secs * 1.0 / TM_DAY, "age-days", "days"); return; } if (secs < TM_MONTH * 2) { print_rel_date(t, tz, secs * 1.0 / TM_WEEK, "age-weeks", "weeks"); return; } if (secs < TM_YEAR * 2) { print_rel_date(t, tz, secs * 1.0 / TM_MONTH, "age-months", "months"); return; } print_rel_date(t, tz, secs * 1.0 / TM_YEAR, "age-years", "years"); } void cgit_print_http_headers(void) { if (ctx.env.no_http && !strcmp(ctx.env.no_http, "1")) return; if (ctx.page.status) htmlf("Status: %d %s\n", ctx.page.status, ctx.page.statusmsg); if (ctx.page.mimetype && ctx.page.charset) htmlf("Content-Type: %s; charset=%s\n", ctx.page.mimetype, ctx.page.charset); else if (ctx.page.mimetype) htmlf("Content-Type: %s\n", ctx.page.mimetype); if (ctx.page.size) htmlf("Content-Length: %zd\n", ctx.page.size); if (ctx.page.filename) { html("Content-Disposition: inline; filename=\""); html_header_arg_in_quotes(ctx.page.filename); html("\"\n"); } if (!ctx.env.authenticated) html("Cache-Control: no-cache, no-store\n"); htmlf("Last-Modified: %s\n", http_date(ctx.page.modified)); htmlf("Expires: %s\n", http_date(ctx.page.expires)); if (ctx.page.etag) htmlf("ETag: \"%s\"\n", ctx.page.etag); html("\n"); if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) exit(0); } void cgit_redirect(const char *url, bool permanent) { htmlf("Status: %d %s\n", permanent ? 301 : 302, permanent ? "Moved" : "Found"); html("Location: "); html_url_path(url); html("\n\n"); } static void print_rel_vcs_link(const char *url) { html("\n"); } void cgit_print_docstart(void) { char *host = cgit_hosturl(); if (ctx.cfg.embedded) { if (ctx.cfg.header) html_include(ctx.cfg.header); return; } html(cgit_doctype); html("\n"); html("\n"); html(""); html_txt(ctx.page.title); html("\n"); htmlf("\n", cgit_version); if (ctx.cfg.robots && *ctx.cfg.robots) htmlf("\n", ctx.cfg.robots); html("\n"); if (ctx.cfg.favicon) { html("\n"); } if (host && ctx.repo && ctx.qry.head) { char *fileurl; struct strbuf sb = STRBUF_INIT; strbuf_addf(&sb, "h=%s", ctx.qry.head); html("\n"); strbuf_release(&sb); free(fileurl); } if (ctx.repo) cgit_add_clone_urls(print_rel_vcs_link); if (ctx.cfg.head_include) html_include(ctx.cfg.head_include); html("\n"); html("\n"); if (ctx.cfg.header) html_include(ctx.cfg.header); free(host); } void cgit_print_docend(void) { html("
\n"); if (ctx.cfg.embedded) { html(" \n"); if (ctx.cfg.footer) html_include(ctx.cfg.footer); return; } if (ctx.cfg.footer) html_include(ctx.cfg.footer); else { htmlf("\n"); } html(" \n"); html("\n\n"); } void cgit_print_error_page(int code, const char *msg, const char *fmt, ...) { va_list ap; ctx.page.expires = ctx.cfg.cache_dynamic_ttl; ctx.page.status = code; ctx.page.statusmsg = msg; cgit_print_layout_start(); va_start(ap, fmt); cgit_vprint_error(fmt, ap); va_end(ap); cgit_print_layout_end(); } void cgit_print_layout_start(void) { cgit_print_http_headers(); cgit_print_docstart(); cgit_print_pageheader(); } void cgit_print_layout_end(void) { cgit_print_docend(); } static void add_clone_urls(void (*fn)(const char *), char *txt, char *suffix) { struct strbuf **url_list = strbuf_split_str(txt, ' ', 0); int i; for (i = 0; url_list[i]; i++) { strbuf_rtrim(url_list[i]); if (url_list[i]->len == 0) continue; if (suffix && *suffix) strbuf_addf(url_list[i], "/%s", suffix); fn(url_list[i]->buf); } strbuf_list_free(url_list); } void cgit_add_clone_urls(void (*fn)(const char *)) { if (ctx.repo->clone_url) add_clone_urls(fn, expand_macros(ctx.repo->clone_url), NULL); else if (ctx.cfg.clone_prefix) add_clone_urls(fn, ctx.cfg.clone_prefix, ctx.repo->url); } static int print_branch_option(const char *refname, const struct object_id *oid, int flags, void *cb_data) { char *name = (char *)refname; html_option(name, name, ctx.qry.head); return 0; } void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page) { if (!ctx.cfg.virtual_root) { struct strbuf url = STRBUF_INIT; strbuf_addf(&url, "%s/%s", ctx.qry.repo, page); if (ctx.qry.vpath) strbuf_addf(&url, "/%s", ctx.qry.vpath); html_hidden("url", url.buf); strbuf_release(&url); } if (incl_head && ctx.qry.head && ctx.repo->defbranch && strcmp(ctx.qry.head, ctx.repo->defbranch)) html_hidden("h", ctx.qry.head); if (ctx.qry.sha1) html_hidden("id", ctx.qry.sha1); if (ctx.qry.sha2) html_hidden("id2", ctx.qry.sha2); if (ctx.qry.showmsg) html_hidden("showmsg", "1"); if (incl_search) { if (ctx.qry.grep) html_hidden("qt", ctx.qry.grep); if (ctx.qry.search) html_hidden("q", ctx.qry.search); } } static const char *hc(const char *page) { if (!ctx.qry.page) return NULL; return strcmp(ctx.qry.page, page) ? NULL : "active"; } static void cgit_print_path_crumbs(char *path) { char *old_path = ctx.qry.path; char *p = path, *q, *end = path + strlen(path); ctx.qry.path = NULL; cgit_self_link("root", NULL, NULL); ctx.qry.path = p = path; while (p < end) { if (!(q = strchr(p, '/'))) q = end; *q = '\0'; html_txt("/"); cgit_self_link(p, NULL, NULL); if (q < end) *q = '/'; p = q + 1; } ctx.qry.path = old_path; } static void print_header(void) { char *logo = NULL, *logo_link = NULL; html("\n"); html("\n"); if (ctx.repo && ctx.repo->logo && *ctx.repo->logo) logo = ctx.repo->logo; else logo = ctx.cfg.logo; if (ctx.repo && ctx.repo->logo_link && *ctx.repo->logo_link) logo_link = ctx.repo->logo_link; else logo_link = ctx.cfg.logo_link; if (logo && *logo) { html("\n"); } html("\n"); html("\n"); } void cgit_print_pageheader(void) { html("
"); if (!ctx.env.authenticated || !ctx.cfg.noheader) print_header(); html("
\n"); if (ctx.env.authenticated && ctx.repo) { if (ctx.repo->readme.nr) reporevlink("about", "about", NULL, hc("about"), ctx.qry.head, NULL, NULL); cgit_summary_link("summary", NULL, hc("summary"), ctx.qry.head); cgit_refs_link("refs", NULL, hc("refs"), ctx.qry.head, ctx.qry.sha1, NULL); cgit_log_link("log", NULL, hc("log"), ctx.qry.head, NULL, ctx.qry.vpath, 0, NULL, NULL, ctx.qry.showmsg, ctx.qry.follow); cgit_tree_link("tree", NULL, hc("tree"), ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath); cgit_commit_link("commit", NULL, hc("commit"), ctx.qry.head, ctx.qry.sha1, ctx.qry.vpath); cgit_diff_link("diff", NULL, hc("diff"), ctx.qry.head, ctx.qry.sha1, ctx.qry.sha2, ctx.qry.vpath); if (ctx.repo->max_stats) cgit_stats_link("stats", NULL, hc("stats"), ctx.qry.head, ctx.qry.vpath); if (ctx.repo->homepage) { html("homepage"); } html(""); html("
\n"); cgit_add_hidden_formfields(1, 0, "log"); html("\n"); html("\n"); html("\n"); html("
\n"); } else if (ctx.env.authenticated) { char *currenturl = cgit_currenturl(); site_link(NULL, "index", NULL, hc("repolist"), NULL, NULL, 0, 1); if (ctx.cfg.root_readme) site_link("about", "about", NULL, hc("about"), NULL, NULL, 0, 1); html("
"); html("
\n"); html("\n"); html("\n"); html("
"); free(currenturl); } html("
\n"); if (ctx.env.authenticated && ctx.qry.vpath) { html("
"); html("path: "); cgit_print_path_crumbs(ctx.qry.vpath); if (ctx.cfg.enable_follow_links && !strcmp(ctx.qry.page, "log")) { html(" ("); ctx.qry.follow = !ctx.qry.follow; cgit_self_link(ctx.qry.follow ? "follow" : "unfollow", NULL, NULL); ctx.qry.follow = !ctx.qry.follow; html(")"); } html("
"); } html("
"); } void cgit_print_filemode(unsigned short mode) { if (S_ISDIR(mode)) html("d"); else if (S_ISLNK(mode)) html("l"); else if (S_ISGITLINK(mode)) html("m"); else html("-"); html_fileperm(mode >> 6); html_fileperm(mode >> 3); html_fileperm(mode); } void cgit_compose_snapshot_prefix(struct strbuf *filename, const char *base, const char *ref) { struct object_id oid; /* * Prettify snapshot names by stripping leading "v" or "V" if the tag * name starts with {v,V}[0-9] and the prettify mapping is injective, * i.e. each stripped tag can be inverted without ambiguities. */ if (get_oid(fmt("refs/tags/%s", ref), &oid) == 0 && (ref[0] == 'v' || ref[0] == 'V') && isdigit(ref[1]) && ((get_oid(fmt("refs/tags/%s", ref + 1), &oid) == 0) + (get_oid(fmt("refs/tags/v%s", ref + 1), &oid) == 0) + (get_oid(fmt("refs/tags/V%s", ref + 1), &oid) == 0) == 1)) ref++; strbuf_addf(filename, "%s-%s", base, ref); } void cgit_print_snapshot_links(const char *repo, const char *head, const char *hex, int snapshots) { const struct cgit_snapshot_format* f; struct strbuf filename = STRBUF_INIT; size_t prefixlen; cgit_compose_snapshot_prefix(&filename, cgit_repobasename(repo), hex); prefixlen = filename.len; for (f = cgit_snapshot_formats; f->suffix; f++) { if (!(snapshots & f->bit)) continue; strbuf_setlen(&filename, prefixlen); strbuf_addstr(&filename, f->suffix); cgit_snapshot_link(filename.buf, NULL, NULL, NULL, NULL, filename.buf); html("
"); } strbuf_release(&filename); } cgit-1.1/ui-shared.h000066400000000000000000000071411301521500400143250ustar00rootroot00000000000000#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 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_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(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 char *repo, const char *head, const char *hex, int snapshots); extern void cgit_add_hidden_formfields(int incl_head, int incl_search, const char *page); #endif /* UI_SHARED_H */ cgit-1.1/ui-snapshot.c000066400000000000000000000137741301521500400147220ustar00rootroot00000000000000/* ui-snapshot.c: generate snapshot of a commit * * Copyright (C) 2006-2014 cgit Development Team * * 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, 1, 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[] = { { ".zip", "application/x-zip", write_zip_archive, 0x01 }, { ".tar.gz", "application/x-gzip", write_tar_gzip_archive, 0x02 }, { ".tar.bz2", "application/x-bzip2", write_tar_bzip2_archive, 0x04 }, { ".tar", "application/x-tar", write_tar_archive, 0x08 }, { ".tar.xz", "application/x-xz", write_tar_xz_archive, 0x10 }, { NULL } }; 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; } 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(oid.hash)) { 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(); format->write_func(hex, prefix); 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 char *url, 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_repobasename(url); 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; char *prefix = NULL; if (!filename) { cgit_print_error_page(400, "Bad request", "No snapshot name specified"); return; } f = get_format(filename); if (!f) { cgit_print_error_page(400, "Bad request", "Unsupported snapshot format: %s", filename); return; } if (!hex && dwim) { hex = get_ref_from_filename(ctx.repo->url, 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_repobasename(ctx.repo->url)); make_snapshot(f, hex, prefix, filename); free(prefix); } cgit-1.1/ui-snapshot.h000066400000000000000000000002611301521500400147120ustar00rootroot00000000000000#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 */ cgit-1.1/ui-ssdiff.c000066400000000000000000000221761301521500400143350ustar00rootroot00000000000000#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); strncpy(buf2, buf1, len); buf2[len] = '\0'; res = atoi(buf2); free(buf2); return res; } static char *replace_tabs(char *line) { char *prev_buf = line; char *cur_buf; int linelen = strlen(line); int n_tabs = 0; int i; char *result; char *spaces = " "; 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 = xmalloc(linelen + n_tabs * 8 + 1); result[0] = '\0'; while (1) { cur_buf = strchr(prev_buf, '\t'); if (!cur_buf) { strcat(result, prev_buf); break; } else { strncat(result, prev_buf, cur_buf - prev_buf); strncat(result, spaces, 8 - (strlen(result) % 8)); } 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("", class); } } else if (line[i] == lcs[j]) { same = 1; htmlf(""); j += 1; } html_txt(c); } } 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("\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("%s", lineno_str, lineno_str + 1); html(""); htmlf("", class); free(fileurl); } else if (old_line) htmlf("", class); else htmlf("", class); if (old_line) { if (lcs) print_part_with_lcs("del", old_line, lcs); else html_txt(old_line); } html("\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("%s", lineno_str, lineno_str + 1); html(""); htmlf("", class); free(fileurl); } else if (new_line) htmlf("", class); else htmlf("", class); if (new_line) { if (lcs) print_part_with_lcs("add", new_line, lcs); else html_txt(new_line); } html(""); 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(""); html_txt(line); html(""); } else { html(""); html_txt(line); html(""); } line[len - 1] = c; } void cgit_ssdiff_header_begin(void) { current_old_line = -1; current_new_line = -1; html("
"); html(""); } void cgit_ssdiff_header_end(void) { html(""); } void cgit_ssdiff_footer(void) { if (deferred_old || deferred_new) cgit_ssdiff_print_deferred_lines(); html(""); } cgit-1.1/ui-ssdiff.h000066400000000000000000000007571301521500400143430ustar00rootroot00000000000000#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 */ cgit-1.1/ui-stats.c000066400000000000000000000232731301521500400142140ustar00rootroot00000000000000#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(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("%s", 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("%ld", centerclass, subtotal); total += subtotal; } htmlf("%ld", 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(""); for (j = 0; j < period->count; j++) { tmp = period->pretty(tm); htmlf("", tmp); period->inc(tm); } html("\n"); if (top <= 0 || top > authors->nr) top = authors->nr; for (i = 0; i < top; i++) { author = &authors->items[i]; html(""); 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(""); else { htmlf("", (uintptr_t)date->util); total += (uintptr_t)date->util; } } htmlf("", 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("
Author%sTotal
"); html_txt(author->string); html("0%lu%ld
"); } /* 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("
"); html("stat options"); html("
"); cgit_add_hidden_formfields(1, 0, "stats"); html(""); if (ctx.repo->max_stats > 1) { html(""); html(""); } html(""); html(""); html("
Period:
Authors:
"); html(""); html("
"); html("
"); html("
"); htmlf("

Commits per author per %s", period->name); if (ctx.qry.path) { html(" (path '"); html_txt(ctx.qry.path); html("')"); } html("

"); print_authors(&authors, top, period); cgit_print_layout_end(); } cgit-1.1/ui-stats.h000066400000000000000000000011601301521500400142100ustar00rootroot00000000000000#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 */ cgit-1.1/ui-summary.c000066400000000000000000000071061301521500400145500ustar00rootroot00000000000000/* ui-summary.c: functions for generating repo summary page * * Copyright (C) 2006-2014 cgit Development Team * * 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(" ", columns); htmlf("Clone\n", columns); } htmlf(""); html_txt(url); html("\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(""); cgit_print_branches(ctx.cfg.summary_branches); htmlf("", columns); cgit_print_tags(ctx.cfg.summary_tags); if (ctx.cfg.summary_log > 0) { htmlf("", 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("
 
 
"); 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(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("
"); 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("
"); if (free_filename) free(filename); done: cgit_print_layout_end(); } cgit-1.1/ui-summary.h000066400000000000000000000002341301521500400145500ustar00rootroot00000000000000#ifndef UI_SUMMARY_H #define UI_SUMMARY_H extern void cgit_print_summary(void); extern void cgit_print_repo_readme(char *path); #endif /* UI_SUMMARY_H */ cgit-1.1/ui-tag.c000066400000000000000000000056351301521500400136330ustar00rootroot00000000000000/* ui-tag.c: display a tag * * Copyright (C) 2006-2014 cgit Development Team * * 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("
"); p = strchr(buf, '\n'); if (p) *p = '\0'; html_txt(buf); html("
"); if (p) { html("
"); html_txt(++p); html("
"); } } static void print_download_links(char *revname) { html("download"); cgit_print_snapshot_links(ctx.qry.repo, ctx.qry.head, revname, ctx.repo->snapshots); html(""); } 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(oid.hash); 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(oid.hash); 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("\n"); htmlf("\n", oid_to_hex(&oid)); if (info->tagger_date > 0) { html("\n"); } if (info->tagger) { html("\n"); } html("\n"); if (ctx.repo->snapshots) print_download_links(revname); html("
tag name"); html_txt(revname); htmlf(" (%s)
tag date"); html_txt(show_date(info->tagger_date, info->tagger_tz, cgit_date_mode(DATE_ISO8601))); html("
tagged by"); 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("
tagged object"); cgit_object_link(tag->tagged); html("
\n"); print_tag_content(info->msg); cgit_print_layout_end(); cgit_free_taginfo(info); } else { cgit_print_layout_start(); html("\n"); htmlf("\n"); html("\n"); if (ctx.repo->snapshots) print_download_links(revname); html("
tag name"); html_txt(revname); html("
Tagged object"); cgit_object_link(obj); html("
\n"); cgit_print_layout_end(); } cleanup: strbuf_release(&fullref); } cgit-1.1/ui-tag.h000066400000000000000000000001451301521500400136270ustar00rootroot00000000000000#ifndef UI_TAG_H #define UI_TAG_H extern void cgit_print_tag(char *revname); #endif /* UI_TAG_H */ cgit-1.1/ui-tree.c000066400000000000000000000230721301521500400140120ustar00rootroot00000000000000/* ui-tree.c: functions for tree output * * Copyright (C) 2006-2014 cgit Development Team * * Licensed under GNU General Public License v2 * (see COPYING for full license text) */ #include "cgit.h" #include "ui-tree.h" #include "html.h" #include "ui-shared.h" struct walk_tree_context { char *curr_rev; char *match_path; int state; }; static void print_text_buffer(const char *name, char *buf, unsigned long size) { unsigned long lineno, idx; const char *numberfmt = "%1$d\n"; html("\n"); if (ctx.cfg.enable_tree_linenumbers) { html("\n"); } else { html("\n"); } if (ctx.repo->source_filter) { char *filter_arg = xstrdup(name); html("
");
		idx = 0;
		lineno = 0;

		if (size) {
			htmlf(numberfmt, ++lineno);
			while (idx < size - 1) { // skip absolute last newline
				if (buf[idx] == '\n')
					htmlf(numberfmt, ++lineno);
				idx++;
			}
		}
		html("
");
		cgit_open_filter(ctx.repo->source_filter, filter_arg);
		html_raw(buf, size);
		cgit_close_filter(ctx.repo->source_filter);
		free(filter_arg);
		html("
\n"); return; } html("
");
	html_txt(buf);
	html("
\n"); } #define ROWLEN 32 static void print_binary_buffer(char *buf, unsigned long size) { unsigned long ofs, idx; static char ascii[ROWLEN + 1]; html("\n"); html(""); for (ofs = 0; ofs < size; ofs += ROWLEN, buf += ROWLEN) { htmlf("\n"); } html("
ofshex dumpascii
%04lx", ofs); for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) htmlf("%*s%02x", idx == 16 ? 4 : 1, "", buf[idx] & 0xff); html(" "); for (idx = 0; idx < ROWLEN && ofs + idx < size; idx++) ascii[idx] = isgraph(buf[idx]) ? buf[idx] : '.'; ascii[idx] = '\0'; html_txt(ascii); html("
\n"); } static void set_title_from_path(const char *path) { size_t path_len, path_index, path_last_end; char *new_title; if (!path) return; path_len = strlen(path); new_title = xmalloc(path_len + 3 + strlen(ctx.page.title) + 1); new_title[0] = '\0'; for (path_index = path_len, path_last_end = path_len; path_index-- > 0;) { if (path[path_index] == '/') { if (path_index == path_len - 1) { path_last_end = path_index - 1; continue; } strncat(new_title, &path[path_index + 1], path_last_end - path_index - 1); strcat(new_title, "\\"); path_last_end = path_index; } } if (path_last_end) strncat(new_title, path, path_last_end); strcat(new_title, " - "); strcat(new_title, ctx.page.title); ctx.page.title = new_title; } static void print_object(const unsigned char *sha1, char *path, const char *basename, const char *rev) { enum object_type type; char *buf; unsigned long size; type = sha1_object_info(sha1, &size); if (type == OBJ_BAD) { cgit_print_error_page(404, "Not found", "Bad object name: %s", sha1_to_hex(sha1)); return; } buf = read_sha1_file(sha1, &type, &size); if (!buf) { cgit_print_error_page(500, "Internal server error", "Error reading object %s", sha1_to_hex(sha1)); return; } set_title_from_path(path); cgit_print_layout_start(); htmlf("blob: %s (", sha1_to_hex(sha1)); cgit_plain_link("plain", NULL, NULL, ctx.qry.head, rev, path); html(")\n"); if (ctx.cfg.max_blob_size && size / 1024 > ctx.cfg.max_blob_size) { htmlf("
blob size (%ldKB) exceeds display size limit (%dKB).
", size / 1024, ctx.cfg.max_blob_size); return; } if (buffer_is_binary(buf, size)) print_binary_buffer(buf, size); else print_text_buffer(basename, buf, size); } struct single_tree_ctx { struct strbuf *path; unsigned char sha1[GIT_SHA1_RAWSZ]; char *name; size_t count; }; static int single_tree_cb(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *cbdata) { struct single_tree_ctx *ctx = cbdata; if (++ctx->count > 1) return -1; if (!S_ISDIR(mode)) { ctx->count = 2; return -1; } ctx->name = xstrdup(pathname); hashcpy(ctx->sha1, sha1); strbuf_addf(ctx->path, "/%s", pathname); return 0; } static void write_tree_link(const unsigned char *sha1, char *name, char *rev, struct strbuf *fullpath) { size_t initial_length = fullpath->len; struct tree *tree; struct single_tree_ctx tree_ctx = { .path = fullpath, .count = 1, }; struct pathspec paths = { .nr = 0 }; hashcpy(tree_ctx.sha1, sha1); while (tree_ctx.count == 1) { cgit_tree_link(name, NULL, "ls-dir", ctx.qry.head, rev, fullpath->buf); tree = lookup_tree(tree_ctx.sha1); if (!tree) return; free(tree_ctx.name); tree_ctx.name = NULL; tree_ctx.count = 0; read_tree_recursive(tree, "", 0, 1, &paths, single_tree_cb, &tree_ctx); if (tree_ctx.count != 1) break; html(" / "); name = tree_ctx.name; } strbuf_setlen(fullpath, initial_length); } static int ls_item(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; char *name; struct strbuf fullpath = STRBUF_INIT; struct strbuf class = STRBUF_INIT; enum object_type type; unsigned long size = 0; name = xstrdup(pathname); strbuf_addf(&fullpath, "%s%s%s", ctx.qry.path ? ctx.qry.path : "", ctx.qry.path ? "/" : "", name); if (!S_ISGITLINK(mode)) { type = sha1_object_info(sha1, &size); if (type == OBJ_BAD) { htmlf("Bad object: %s %s", name, sha1_to_hex(sha1)); free(name); return 0; } } html(""); cgit_print_filemode(mode); html(""); if (S_ISGITLINK(mode)) { cgit_submodule_link("ls-mod", fullpath.buf, sha1_to_hex(sha1)); } else if (S_ISDIR(mode)) { write_tree_link(sha1, name, walk_tree_ctx->curr_rev, &fullpath); } else { char *ext = strrchr(name, '.'); strbuf_addstr(&class, "ls-blob"); if (ext) strbuf_addf(&class, " %s", ext + 1); cgit_tree_link(name, NULL, class.buf, ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); } htmlf("%li", size); html(""); cgit_log_link("log", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf, 0, NULL, NULL, ctx.qry.showmsg, 0); if (ctx.repo->max_stats) cgit_stats_link("stats", NULL, "button", ctx.qry.head, fullpath.buf); if (!S_ISGITLINK(mode)) cgit_plain_link("plain", NULL, "button", ctx.qry.head, walk_tree_ctx->curr_rev, fullpath.buf); html("\n"); free(name); strbuf_release(&fullpath); strbuf_release(&class); return 0; } static void ls_head(void) { cgit_print_layout_start(); html("\n"); html(""); html(""); html(""); html(""); html("\n"); } static void ls_tail(void) { html("
ModeNameSize"); html("
\n"); cgit_print_layout_end(); } static void ls_tree(const unsigned char *sha1, char *path, struct walk_tree_context *walk_tree_ctx) { struct tree *tree; struct pathspec paths = { .nr = 0 }; tree = parse_tree_indirect(sha1); if (!tree) { cgit_print_error_page(404, "Not found", "Not a tree object: %s", sha1_to_hex(sha1)); return; } ls_head(); read_tree_recursive(tree, "", 0, 1, &paths, ls_item, walk_tree_ctx); ls_tail(); } static int walk_tree(const unsigned char *sha1, struct strbuf *base, const char *pathname, unsigned mode, int stage, void *cbdata) { struct walk_tree_context *walk_tree_ctx = cbdata; if (walk_tree_ctx->state == 0) { struct strbuf buffer = STRBUF_INIT; strbuf_addbuf(&buffer, base); strbuf_addstr(&buffer, pathname); if (strcmp(walk_tree_ctx->match_path, buffer.buf)) return READ_TREE_RECURSIVE; if (S_ISDIR(mode)) { walk_tree_ctx->state = 1; set_title_from_path(buffer.buf); strbuf_release(&buffer); ls_head(); return READ_TREE_RECURSIVE; } else { walk_tree_ctx->state = 2; print_object(sha1, buffer.buf, pathname, walk_tree_ctx->curr_rev); strbuf_release(&buffer); return 0; } } ls_item(sha1, base, pathname, mode, stage, walk_tree_ctx); return 0; } /* * Show a tree or a blob * rev: the commit pointing at the root tree object * path: path to tree or blob */ void cgit_print_tree(const char *rev, char *path) { struct object_id oid; struct commit *commit; struct pathspec_item path_items = { .match = path, .len = path ? strlen(path) : 0 }; struct pathspec paths = { .nr = path ? 1 : 0, .items = &path_items }; struct walk_tree_context walk_tree_ctx = { .match_path = path, .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(oid.hash); 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); if (path == NULL) { ls_tree(commit->tree->object.oid.hash, NULL, &walk_tree_ctx); goto cleanup; } read_tree_recursive(commit->tree, "", 0, 0, &paths, walk_tree, &walk_tree_ctx); if (walk_tree_ctx.state == 1) ls_tail(); else if (walk_tree_ctx.state == 2) cgit_print_layout_end(); else cgit_print_error_page(404, "Not found", "Path not found"); cleanup: free(walk_tree_ctx.curr_rev); } cgit-1.1/ui-tree.h000066400000000000000000000001671301521500400140170ustar00rootroot00000000000000#ifndef UI_TREE_H #define UI_TREE_H extern void cgit_print_tree(const char *rev, char *path); #endif /* UI_TREE_H */ pax_global_header00006660000000000000000000000064130046732440014515gustar00rootroot0000000000000052 comment=ac84098b7e32406a982ac01cc76a663d5605224b cgit-1.1/git/000077500000000000000000000000001300467324400130675ustar00rootroot00000000000000cgit-1.1/git/.gitattributes000066400000000000000000000001621300467324400157610ustar00rootroot00000000000000* whitespace=!indent,trail,space *.[ch] whitespace=indent,trail,space diff=cpp *.sh whitespace=indent,trail,space cgit-1.1/git/.gitignore000066400000000000000000000061771300467324400150720ustar00rootroot00000000000000/GIT-BUILD-OPTIONS /GIT-CFLAGS /GIT-LDFLAGS /GIT-PREFIX /GIT-PERL-DEFINES /GIT-PYTHON-VARS /GIT-SCRIPT-DEFINES /GIT-USER-AGENT /GIT-VERSION-FILE /bin-wrappers/ /git /git-add /git-add--interactive /git-am /git-annotate /git-apply /git-archimport /git-archive /git-bisect /git-bisect--helper /git-blame /git-branch /git-bundle /git-cat-file /git-check-attr /git-check-ignore /git-check-mailmap /git-check-ref-format /git-checkout /git-checkout-index /git-cherry /git-cherry-pick /git-clean /git-clone /git-column /git-commit /git-commit-tree /git-config /git-count-objects /git-credential /git-credential-cache /git-credential-cache--daemon /git-credential-store /git-cvsexportcommit /git-cvsimport /git-cvsserver /git-daemon /git-diff /git-diff-files /git-diff-index /git-diff-tree /git-difftool /git-difftool--helper /git-describe /git-fast-export /git-fast-import /git-fetch /git-fetch-pack /git-filter-branch /git-fmt-merge-msg /git-for-each-ref /git-format-patch /git-fsck /git-fsck-objects /git-gc /git-get-tar-commit-id /git-grep /git-hash-object /git-help /git-http-backend /git-http-fetch /git-http-push /git-imap-send /git-index-pack /git-init /git-init-db /git-interpret-trailers /git-instaweb /git-log /git-ls-files /git-ls-remote /git-ls-tree /git-mailinfo /git-mailsplit /git-merge /git-merge-base /git-merge-index /git-merge-file /git-merge-tree /git-merge-octopus /git-merge-one-file /git-merge-ours /git-merge-recursive /git-merge-resolve /git-merge-subtree /git-mergetool /git-mergetool--lib /git-mktag /git-mktree /git-name-rev /git-mv /git-notes /git-p4 /git-pack-redundant /git-pack-objects /git-pack-refs /git-parse-remote /git-patch-id /git-prune /git-prune-packed /git-pull /git-push /git-quiltimport /git-read-tree /git-rebase /git-rebase--am /git-rebase--interactive /git-rebase--merge /git-receive-pack /git-reflog /git-relink /git-remote /git-remote-http /git-remote-https /git-remote-ftp /git-remote-ftps /git-remote-fd /git-remote-ext /git-remote-testgit /git-remote-testpy /git-remote-testsvn /git-repack /git-replace /git-request-pull /git-rerere /git-reset /git-rev-list /git-rev-parse /git-revert /git-rm /git-send-email /git-send-pack /git-sh-i18n /git-sh-i18n--envsubst /git-sh-setup /git-sh-i18n /git-shell /git-shortlog /git-show /git-show-branch /git-show-index /git-show-ref /git-stage /git-stash /git-status /git-stripspace /git-submodule /git-submodule--helper /git-svn /git-symbolic-ref /git-tag /git-unpack-file /git-unpack-objects /git-update-index /git-update-ref /git-update-server-info /git-upload-archive /git-upload-pack /git-var /git-verify-commit /git-verify-pack /git-verify-tag /git-web--browse /git-whatchanged /git-worktree /git-write-tree /git-core-*/?* /gitweb/GITWEB-BUILD-OPTIONS /gitweb/gitweb.cgi /gitweb/static/gitweb.js /gitweb/static/gitweb.min.* /common-cmds.h *.tar.gz *.dsc *.deb /git.spec *.exe *.[aos] *.py[co] .depend/ *.gcda *.gcno *.gcov /coverage-untested-functions /cover_db/ /cover_db_html/ *+ /config.mak /autom4te.cache /config.cache /config.log /config.status /config.mak.autogen /config.mak.append /configure /unicode /tags /TAGS /cscope* *.obj *.lib *.res *.sln *.suo *.ncb *.vcproj *.user *.idb *.pdb /Debug/ /Release/ cgit-1.1/git/.mailmap000066400000000000000000000342371300467324400145210ustar00rootroot00000000000000# # This list is used by git-shortlog to fix a few botched name translations # in the git archive, either because the author's full name was messed up # and/or not always written the same way, making contributions from the # same person appearing not to be so. # Alejandro R. Sedeño Alex Bennée Alex Riesen Alex Riesen Alex Riesen Alex Vandiver Alexander Gavrilov Alexander Kuleshov Alexey Shumkin Alexey Shumkin Anders Kaseorg Anders Kaseorg Aneesh Kumar K.V Amos Waterland Amos Waterland Ben Walton Benoit Sigoure Bernt Hansen Brandon Casey brian m. carlson Brian M. Carlson brian m. carlson Bryan Larsen Bryan Larsen Cheng Renquan Chris Shoemaker Chris Wright Cord Seele Christian Couder Christian Stimming Csaba Henk Dan Johnson Dana L. How Dana L. How Dana How Daniel Barkalow Daniel Trstenjak Daniel Trstenjak David Brown David D. Kilzer David Kågedal David Reiss David S. Miller David Turner David Turner Deskin Miller Dirk Süsserott Eric Blake Eric Hanchrow Eric S. Raymond Eric Wong Erik Faye-Lund Eyvind Bernhardsen Florian Achleitner Franck Bui-Huu Frank Lichtenheld Frank Lichtenheld Fredrik Kuivinen Frédéric Heitzmann Garry Dolley Greg Price Greg Price Heiko Voigt H. Merijn Brand H.Merijn Brand H. Peter Anvin H. Peter Anvin H. Peter Anvin H. Peter Anvin Han-Wen Nienhuys Han-Wen Nienhuys Horst H. von Brand J. Bruce Fields J. Bruce Fields J. Bruce Fields Jakub Narębski James Y Knight # The 2 following authors are probably the same person, # but both emails bounce. Jason McMullan Jason McMullan Jason Riedy Jason Riedy Jay Soffian Jeff King Jeff Muizelaar Jens Axboe Jens Axboe Jens Lindström Jens Lindstrom Jim Meyering Joachim Berdal Haga Johannes Schindelin Johannes Sixt Johannes Sixt Johannes Sixt John 'Warthog9' Hawley Jon Loeliger Jon Loeliger Jon Seymour Jonathan Nieder Jonathan del Strother Josh Triplett Josh Triplett Julian Phillips Junio C Hamano Junio C Hamano Junio C Hamano Junio C Hamano Junio C Hamano Junio C Hamano Junio C Hamano Karl Wiberg Karl Hasselström Karl Wiberg Karsten Blees Karsten Blees Kay Sievers Kay Sievers Kazuki Saitoh kazuki saitoh Keith Cascio Kent Engstrom Kevin Leung Kirill Smelkov Kirill Smelkov Knut Franke Lars Doelle Lars Doelle Lars Noschinski Li Hong Linus Torvalds Linus Torvalds Linus Torvalds Linus Torvalds Linus Torvalds Linus Torvalds Lukas Sandström Marc Khouzam Marc-André Lureau Marco Costalba Mark Levedahl Mark Rada Martin Langhoff Martin von Zweigbergk Matt Draisey Matt Kraai Matt McCutchen Matthias Kestenholz Matthias Urlichs Matthias Urlichs Michael Coleman Michael J Gruber Michael S. Tsirkin Michael S. Tsirkin Michael S. Tsirkin Michael W. Olson Michael Witten Michael Witten Michal Rokos Michele Ballabio Miklos Vajna Namhyung Kim Namhyung Kim Nanako Shiraishi Nanako Shiraishi Nelson Elhage Nelson Elhage Nguyễn Thái Ngọc Duy Nick Stokoe Nick Woolley Nick Stokoe Nick Woolley Nicolas Morey-Chaisemartin Nicolas Morey-Chaisemartin Nicolas Sebrecht Paolo Bonzini Pascal Obry Pascal Obry Pat Notz Paul Mackerras Paul Mackerras Peter Baumann Peter Baumann Peter Krefting Peter Krefting Petr Baudis Petr Baudis Phil Hord Philip Jägenstedt Philipp A. Hartmann Philippe Bruhat Ralf Thielow Ramsay Jones René Scharfe Robert Fitzsimons Robert Shearman Robert Zeh Robin Rosenberg Rutger Nijlunsing Rutger Nijlunsing Ryan Anderson Salikh Zakirov Sam Vilain Sam Vilain sam@vilain.net Santi Béjar Sean Estabrooks Sebastian Schuberth Seth Falcon Shawn O. Pearce Simon Hausmann Simon Hausmann Stefan Beller Stefan Beller Stefan Naewe Stefan Naewe Stefan Sperling Štěpán Němec Stephen Boyd Steven Drake Steven Grimm Steven Grimm koreth@midwinter.com Steven Walter Steven Walter Sven Verdoolaege Sven Verdoolaege Tay Ray Chuan Ted Percival Theodore Ts'o Thomas Ackermann Thomas Rast Thomas Rast Thomas Rast Timo Hirvonen Toby Allsopp Tom Grennan Tommi Virtanen Tommi Virtanen Tommy Thorn Tony Luck Tor Arne Vestbø Trần Ngọc Quân Tran Ngoc Quan Trent Piepho Trent Piepho Uwe Kleine-König Uwe Kleine-König Uwe Kleine-König Uwe Kleine-König Ville Skyttä Vitaly "_Vi" Shukela W. Trevor King William Pursell YONETANI Tomokazu YONETANI Tomokazu YOSHIFUJI Hideaki # the two anonymous contributors are different persons: anonymous anonymous İsmail Dönmez cgit-1.1/git/.travis.yml000066400000000000000000000070211300467324400152000ustar00rootroot00000000000000language: c sudo: false cache: directories: - $HOME/travis-cache os: - linux - osx compiler: - clang - gcc addons: apt: packages: - language-pack-is - git-svn - apache2 env: global: - DEVELOPER=1 # The Linux build installs the defined dependency versions below. # The OS X build installs the latest available versions. Keep that # in mind when you encounter a broken OS X build! - LINUX_P4_VERSION="16.1" - LINUX_GIT_LFS_VERSION="1.2.0" - DEFAULT_TEST_TARGET=prove - GIT_PROVE_OPTS="--timer --jobs 3 --state=failed,slow,save" - GIT_TEST_OPTS="--verbose-log" - GIT_TEST_HTTPD=true - GIT_TEST_CLONE_2GB=YesPlease # t9810 occasionally fails on Travis CI OS X # t9816 occasionally fails with "TAP out of sequence errors" on Travis CI OS X - GIT_SKIP_TESTS="t9810 t9816" matrix: include: - env: Documentation os: linux compiler: clang addons: apt: packages: - asciidoc - xmlto before_install: before_script: script: ci/test-documentation.sh after_failure: before_install: - > case "${TRAVIS_OS_NAME:-linux}" in linux) mkdir --parents custom/p4 pushd custom/p4 wget --quiet http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION/bin.linux26x86_64/p4d wget --quiet http://filehost.perforce.com/perforce/r$LINUX_P4_VERSION/bin.linux26x86_64/p4 chmod u+x p4d chmod u+x p4 export PATH="$(pwd):$PATH" popd mkdir --parents custom/git-lfs pushd custom/git-lfs wget --quiet https://github.com/github/git-lfs/releases/download/v$LINUX_GIT_LFS_VERSION/git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz tar --extract --gunzip --file "git-lfs-linux-amd64-$LINUX_GIT_LFS_VERSION.tar.gz" cp git-lfs-$LINUX_GIT_LFS_VERSION/git-lfs . export PATH="$(pwd):$PATH" popd ;; osx) brew_force_set_latest_binary_hash () { FORMULA=$1 SHA=$(brew fetch --force $FORMULA 2>&1 | grep ^SHA256: | cut -d ' ' -f 2) sed -E -i.bak "s/sha256 \"[0-9a-f]{64}\"/sha256 \"$SHA\"/g" \ "$(brew --repository homebrew/homebrew-binary)/$FORMULA.rb" } brew update --quiet brew tap homebrew/binary --quiet brew_force_set_latest_binary_hash perforce brew_force_set_latest_binary_hash perforce-server # Uncomment this if you want to run perf tests: # brew install gnu-time brew install git-lfs perforce-server perforce gettext brew link --force gettext ;; esac; echo "$(tput setaf 6)Perforce Server Version$(tput sgr0)"; p4d -V | grep Rev.; echo "$(tput setaf 6)Perforce Client Version$(tput sgr0)"; p4 -V | grep Rev.; echo "$(tput setaf 6)Git-LFS Version$(tput sgr0)"; git-lfs version; mkdir -p $HOME/travis-cache; ln -s $HOME/travis-cache/.prove t/.prove; before_script: make --jobs=2 script: make --quiet test after_failure: - > : '<-- Click here to see detailed test output! '; for TEST_EXIT in t/test-results/*.exit; do if [ "$(cat "$TEST_EXIT")" != "0" ]; then TEST_OUT="${TEST_EXIT%exit}out"; echo "------------------------------------------------------------------------"; echo "$(tput setaf 1)${TEST_OUT}...$(tput sgr0)"; echo "------------------------------------------------------------------------"; cat "${TEST_OUT}"; fi; done; notifications: email: false cgit-1.1/git/COPYING000066400000000000000000000445151300467324400141330ustar00rootroot00000000000000 Note that the only valid version of the GPL as far as this project is concerned is _this_ particular version of the license (ie v2, not v2.2 or v3.x or whatever), unless explicitly otherwise stated. HOWEVER, in order to allow a migration to GPLv3 if that seems like a good idea, I also ask that people involved with the project make their preferences known. In particular, if you trust me to make that decision, you might note so in your copyright message, ie something like This file is licensed under the GPL v2, or a later version at the discretion of Linus. might avoid issues. But we can also just decide to synchronize and contact all copyright holders on record if/when the occasion arises. Linus Torvalds ---------------------------------------- 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. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. cgit-1.1/git/Documentation/000077500000000000000000000000001300467324400157005ustar00rootroot00000000000000cgit-1.1/git/Documentation/.gitattributes000066400000000000000000000000211300467324400205640ustar00rootroot00000000000000*.txt whitespace cgit-1.1/git/Documentation/.gitignore000066400000000000000000000002071300467324400176670ustar00rootroot00000000000000*.xml *.html *.[1-8] *.made *.texi *.pdf git.info gitman.info howto-index.txt doc.dep cmds-*.txt mergetools-*.txt manpage-base-url.xsl cgit-1.1/git/Documentation/CodingGuidelines000066400000000000000000000451261300467324400210470ustar00rootroot00000000000000Like other projects, we also have some guidelines to keep to the code. For Git in general, a few rough rules are: - Most importantly, we never say "It's in POSIX; we'll happily ignore your needs should your system not conform to it." We live in the real world. - However, we often say "Let's stay away from that construct, it's not even in POSIX". - In spite of the above two rules, we sometimes say "Although this is not in POSIX, it (is so convenient | makes the code much more readable | has other good characteristics) and practically all the platforms we care about support it, so let's use it". Again, we live in the real world, and it is sometimes a judgement call, the decision based more on real world constraints people face than what the paper standard says. - Fixing style violations while working on a real change as a preparatory clean-up step is good, but otherwise avoid useless code churn for the sake of conforming to the style. "Once it _is_ in the tree, it's not really worth the patch noise to go and fix it up." Cf. http://article.gmane.org/gmane.linux.kernel/943020 Make your code readable and sensible, and don't try to be clever. As for more concrete guidelines, just imitate the existing code (this is a good guideline, no matter which project you are contributing to). It is always preferable to match the _local_ convention. New code added to Git suite is expected to match the overall style of existing code. Modifications to existing code is expected to match the style the surrounding code already uses (even if it doesn't match the overall style of existing code). But if you must have a list of rules, here they are. For shell scripts specifically (not exhaustive): - We use tabs for indentation. - Case arms are indented at the same depth as case and esac lines, like this: case "$variable" in pattern1) do this ;; pattern2) do that ;; esac - Redirection operators should be written with space before, but no space after them. In other words, write 'echo test >"$file"' instead of 'echo test> $file' or 'echo test > $file'. Note that even though it is not required by POSIX to double-quote the redirection target in a variable (as shown above), our code does so because some versions of bash issue a warning without the quotes. (incorrect) cat hello > world < universe echo hello >$world (correct) cat hello >world "$world" - We prefer $( ... ) for command substitution; unlike ``, it properly nests. It should have been the way Bourne spelled it from day one, but unfortunately isn't. - If you want to find out if a command is available on the user's $PATH, you should use 'type ', instead of 'which '. The output of 'which' is not machine parseable and its exit code is not reliable across platforms. - We use POSIX compliant parameter substitutions and avoid bashisms; namely: - We use ${parameter-word} and its [-=?+] siblings, and their colon'ed "unset or null" form. - We use ${parameter#word} and its [#%] siblings, and their doubled "longest matching" form. - No "Substring Expansion" ${parameter:offset:length}. - No shell arrays. - No strlen ${#parameter}. - No pattern replacement ${parameter/pattern/string}. - We use Arithmetic Expansion $(( ... )). - Inside Arithmetic Expansion, spell shell variables with $ in front of them, as some shells do not grok $((x)) while accepting $(($x)) just fine (e.g. dash older than 0.5.4). - We do not use Process Substitution <(list) or >(list). - Do not write control structures on a single line with semicolon. "then" should be on the next line for if statements, and "do" should be on the next line for "while" and "for". (incorrect) if test -f hello; then do this fi (correct) if test -f hello then do this fi - We prefer "test" over "[ ... ]". - We do not write the noiseword "function" in front of shell functions. - We prefer a space between the function name and the parentheses, and no space inside the parentheses. The opening "{" should also be on the same line. (incorrect) my_function(){ ... (correct) my_function () { ... - As to use of grep, stick to a subset of BRE (namely, no \{m,n\}, [::], [==], or [..]) for portability. - We do not use \{m,n\}; - We do not use -E; - We do not use ? or + (which are \{0,1\} and \{1,\} respectively in BRE) but that goes without saying as these are ERE elements not BRE (note that \? and \+ are not even part of BRE -- making them accessible from BRE is a GNU extension). - Use Git's gettext wrappers in git-sh-i18n to make the user interface translatable. See "Marking strings for translation" in po/README. - We do not write our "test" command with "-a" and "-o" and use "&&" or "||" to concatenate multiple "test" commands instead, because the use of "-a/-o" is often error-prone. E.g. test -n "$x" -a "$a" = "$b" is buggy and breaks when $x is "=", but test -n "$x" && test "$a" = "$b" does not have such a problem. For C programs: - We use tabs to indent, and interpret tabs as taking up to 8 spaces. - We try to keep to at most 80 characters per line. - As a Git developer we assume you have a reasonably modern compiler and we recommend you to enable the DEVELOPER makefile knob to ensure your patch is clear of all compiler warnings we care about, by e.g. "echo DEVELOPER=1 >>config.mak". - We try to support a wide range of C compilers to compile Git with, including old ones. That means that you should not use C99 initializers, even if a lot of compilers grok it. - Variables have to be declared at the beginning of the block. - NULL pointers shall be written as NULL, not as 0. - When declaring pointers, the star sides with the variable name, i.e. "char *string", not "char* string" or "char * string". This makes it easier to understand code like "char *string, c;". - Use whitespace around operators and keywords, but not inside parentheses and not around functions. So: while (condition) func(bar + 1); and not: while( condition ) func (bar+1); - We avoid using braces unnecessarily. I.e. if (bla) { x = 1; } is frowned upon. A gray area is when the statement extends over a few lines, and/or you have a lengthy comment atop of it. Also, like in the Linux kernel, if there is a long list of "else if" statements, it can make sense to add braces to single line blocks. - We try to avoid assignments in the condition of an "if" statement. - Try to make your code understandable. You may put comments in, but comments invariably tend to stale out when the code they were describing changes. Often splitting a function into two makes the intention of the code much clearer. - Multi-line comments include their delimiters on separate lines from the text. E.g. /* * A very long * multi-line comment. */ Note however that a comment that explains a translatable string to translators uses a convention of starting with a magic token "TRANSLATORS: " immediately after the opening delimiter, even when it spans multiple lines. We do not add an asterisk at the beginning of each line, either. E.g. /* TRANSLATORS: here is a comment that explains the string to be translated, that follows immediately after it */ _("Here is a translatable string explained by the above."); - Double negation is often harder to understand than no negation at all. - There are two schools of thought when it comes to comparison, especially inside a loop. Some people prefer to have the less stable value on the left hand side and the more stable value on the right hand side, e.g. if you have a loop that counts variable i down to the lower bound, while (i > lower_bound) { do something; i--; } Other people prefer to have the textual order of values match the actual order of values in their comparison, so that they can mentally draw a number line from left to right and place these values in order, i.e. while (lower_bound < i) { do something; i--; } Both are valid, and we use both. However, the more "stable" the stable side becomes, the more we tend to prefer the former (comparison with a constant, "i > 0", is an extreme example). Just do not mix styles in the same part of the code and mimic existing styles in the neighbourhood. - There are two schools of thought when it comes to splitting a long logical line into multiple lines. Some people push the second and subsequent lines far enough to the right with tabs and align them: if (the_beginning_of_a_very_long_expression_that_has_to || span_more_than_a_single_line_of || the_source_text) { ... while other people prefer to align the second and the subsequent lines with the column immediately inside the opening parenthesis, with tabs and spaces, following our "tabstop is always a multiple of 8" convention: if (the_beginning_of_a_very_long_expression_that_has_to || span_more_than_a_single_line_of || the_source_text) { ... Both are valid, and we use both. Again, just do not mix styles in the same part of the code and mimic existing styles in the neighbourhood. - When splitting a long logical line, some people change line before a binary operator, so that the result looks like a parse tree when you turn your head 90-degrees counterclockwise: if (the_beginning_of_a_very_long_expression_that_has_to || span_more_than_a_single_line_of_the_source_text) { while other people prefer to leave the operator at the end of the line: if (the_beginning_of_a_very_long_expression_that_has_to || span_more_than_a_single_line_of_the_source_text) { Both are valid, but we tend to use the latter more, unless the expression gets fairly complex, in which case the former tends to be easier to read. Again, just do not mix styles in the same part of the code and mimic existing styles in the neighbourhood. - When splitting a long logical line, with everything else being equal, it is preferable to split after the operator at higher level in the parse tree. That is, this is more preferable: if (a_very_long_variable * that_is_used_in + a_very_long_expression) { ... than if (a_very_long_variable * that_is_used_in + a_very_long_expression) { ... - Some clever tricks, like using the !! operator with arithmetic constructs, can be extremely confusing to others. Avoid them, unless there is a compelling reason to use them. - Use the API. No, really. We have a strbuf (variable length string), several arrays with the ALLOC_GROW() macro, a string_list for sorted string lists, a hash map (mapping struct objects) named "struct decorate", amongst other things. - When you come up with an API, document it. - The first #include in C files, except in platform specific compat/ implementations, must be either "git-compat-util.h", "cache.h" or "builtin.h". You do not have to include more than one of these. - A C file must directly include the header files that declare the functions and the types it uses, except for the functions and types that are made available to it by including one of the header files it must include by the previous rule. - If you are planning a new command, consider writing it in shell or perl first, so that changes in semantics can be easily changed and discussed. Many Git commands started out like that, and a few are still scripts. - Avoid introducing a new dependency into Git. This means you usually should stay away from scripting languages not already used in the Git core command set (unless your command is clearly separate from it, such as an importer to convert random-scm-X repositories to Git). - When we pass pair to functions, we should try to pass them in that order. - Use Git's gettext wrappers to make the user interface translatable. See "Marking strings for translation" in po/README. For Perl programs: - Most of the C guidelines above apply. - We try to support Perl 5.8 and later ("use Perl 5.008"). - use strict and use warnings are strongly preferred. - Don't overuse statement modifiers unless using them makes the result easier to follow. ... do something ... do_this() unless (condition); ... do something else ... is more readable than: ... do something ... unless (condition) { do_this(); } ... do something else ... *only* when the condition is so rare that do_this() will be almost always called. - We try to avoid assignments inside "if ()" conditions. - Learn and use Git.pm if you need that functionality. - For Emacs, it's useful to put the following in GIT_CHECKOUT/.dir-locals.el, assuming you use cperl-mode: ;; note the first part is useful for C editing, too ((nil . ((indent-tabs-mode . t) (tab-width . 8) (fill-column . 80))) (cperl-mode . ((cperl-indent-level . 8) (cperl-extra-newline-before-brace . nil) (cperl-merge-trailing-else . t)))) For Python scripts: - We follow PEP-8 (http://www.python.org/dev/peps/pep-0008/). - As a minimum, we aim to be compatible with Python 2.6 and 2.7. - Where required libraries do not restrict us to Python 2, we try to also be compatible with Python 3.1 and later. - When you must differentiate between Unicode literals and byte string literals, it is OK to use the 'b' prefix. Even though the Python documentation for version 2.6 does not mention this prefix, it has been supported since version 2.6.0. Error Messages - Do not end error messages with a full stop. - Do not capitalize ("unable to open %s", not "Unable to open %s") - Say what the error is first ("cannot open %s", not "%s: cannot open") Externally Visible Names - For configuration variable names, follow the existing convention: . The section name indicates the affected subsystem. . The subsection name, if any, indicates which of an unbounded set of things to set the value for. . The variable name describes the effect of tweaking this knob. The section and variable names that consist of multiple words are formed by concatenating the words without punctuations (e.g. `-`), and are broken using bumpyCaps in documentation as a hint to the reader. When choosing the variable namespace, do not use variable name for specifying possibly unbounded set of things, most notably anything an end user can freely come up with (e.g. branch names). Instead, use subsection names or variable values, like the existing variable branch..description does. Writing Documentation: Most (if not all) of the documentation pages are written in the AsciiDoc format in *.txt files (e.g. Documentation/git.txt), and processed into HTML and manpages (e.g. git.html and git.1 in the same directory). The documentation liberally mixes US and UK English (en_US/UK) norms for spelling and grammar, which is somewhat unfortunate. In an ideal world, it would have been better if it consistently used only one and not the other, and we would have picked en_US (if you wish to correct the English of some of the existing documentation, please see the documentation-related advice in the Documentation/SubmittingPatches file). Every user-visible change should be reflected in the documentation. The same general rule as for code applies -- imitate the existing conventions. A few commented examples follow to provide reference when writing or modifying command usage strings and synopsis sections in the manual pages: Placeholders are spelled in lowercase and enclosed in angle brackets: --sort= --abbrev[=] If a placeholder has multiple words, they are separated by dashes: --template= Possibility of multiple occurrences is indicated by three dots: ... (One or more of .) Optional parts are enclosed in square brackets: [] (Zero or one .) --exec-path[=] (Option with an optional argument. Note that the "=" is inside the brackets.) [...] (Zero or more of . Note that the dots are inside, not outside the brackets.) Multiple alternatives are indicated with vertical bars: [-q | --quiet] [--utf8 | --no-utf8] Parentheses are used for grouping: [( | )...] (Any number of either or . Parens are needed to make it clear that "..." pertains to both and .) [(-p )...] (Any number of option -p, each with one argument.) git remote set-head (-a | -d | ) (One and only one of "-a", "-d" or "" _must_ (no square brackets) be provided.) And a somewhat more contrived example: --diff-filter=[(A|C|D|M|R|T|U|X|B)...[*]] Here "=" is outside the brackets, because "--diff-filter=" is a valid usage. "*" has its own pair of brackets, because it can (optionally) be specified only when one or more of the letters is also provided. A note on notation: Use 'git' (all lowercase) when talking about commands i.e. something the user would type into a shell and use 'Git' (uppercase first letter) when talking about the version control system and its properties. A few commented examples follow to provide reference when writing or modifying paragraphs or option/command explanations that contain options or commands: Literal examples (e.g. use of command-line options, command names, branch names, configuration and environment variables) must be typeset in monospace (i.e. wrapped with backticks): `--pretty=oneline` `git rev-list` `remote.pushDefault` `GIT_DIR` `HEAD` An environment variable must be prefixed with "$" only when referring to its value and not when referring to the variable itself, in this case there is nothing to add except the backticks: `GIT_DIR` is specified `$GIT_DIR/hooks/pre-receive` Word phrases enclosed in `backtick characters` are rendered literally and will not be further expanded. The use of `backticks` to achieve the previous rule means that literal examples should not use AsciiDoc escapes. Correct: `--pretty=oneline` Incorrect: `\--pretty=oneline` If some place in the documentation needs to typeset a command usage example with inline substitutions, it is fine to use +monospaced and inline substituted text+ instead of `monospaced literal text`, and with the former, the part that should not get substituted must be quoted/escaped. cgit-1.1/git/Documentation/Makefile000066400000000000000000000307521300467324400173470ustar00rootroot00000000000000# Guard against environment variables MAN1_TXT = MAN5_TXT = MAN7_TXT = TECH_DOCS = ARTICLES = SP_ARTICLES = OBSOLETE_HTML = MAN1_TXT += $(filter-out \ $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \ $(wildcard git-*.txt)) MAN1_TXT += git.txt MAN1_TXT += gitk.txt MAN1_TXT += gitremote-helpers.txt MAN1_TXT += gitweb.txt MAN5_TXT += gitattributes.txt MAN5_TXT += githooks.txt MAN5_TXT += gitignore.txt MAN5_TXT += gitmodules.txt MAN5_TXT += gitrepository-layout.txt MAN5_TXT += gitweb.conf.txt MAN7_TXT += gitcli.txt MAN7_TXT += gitcore-tutorial.txt MAN7_TXT += gitcredentials.txt MAN7_TXT += gitcvs-migration.txt MAN7_TXT += gitdiffcore.txt MAN7_TXT += giteveryday.txt MAN7_TXT += gitglossary.txt MAN7_TXT += gitnamespaces.txt MAN7_TXT += gitrevisions.txt MAN7_TXT += gittutorial-2.txt MAN7_TXT += gittutorial.txt MAN7_TXT += gitworkflows.txt MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT) MAN_XML = $(patsubst %.txt,%.xml,$(MAN_TXT)) MAN_HTML = $(patsubst %.txt,%.html,$(MAN_TXT)) OBSOLETE_HTML += everyday.html OBSOLETE_HTML += git-remote-helpers.html DOC_HTML = $(MAN_HTML) $(OBSOLETE_HTML) ARTICLES += howto-index ARTICLES += git-tools ARTICLES += git-bisect-lk2009 # with their own formatting rules. SP_ARTICLES += user-manual SP_ARTICLES += howto/new-command SP_ARTICLES += howto/revert-branch-rebase SP_ARTICLES += howto/using-merge-subtree SP_ARTICLES += howto/using-signed-tag-in-pull-request SP_ARTICLES += howto/use-git-daemon SP_ARTICLES += howto/update-hook-example SP_ARTICLES += howto/setup-git-server-over-http SP_ARTICLES += howto/separating-topic-branches SP_ARTICLES += howto/revert-a-faulty-merge SP_ARTICLES += howto/recover-corrupted-blob-object SP_ARTICLES += howto/recover-corrupted-object-harder SP_ARTICLES += howto/rebuild-from-update-hook SP_ARTICLES += howto/rebase-from-internal-branch SP_ARTICLES += howto/keep-canonical-history-correct SP_ARTICLES += howto/maintain-git API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt))) SP_ARTICLES += $(API_DOCS) TECH_DOCS += technical/http-protocol TECH_DOCS += technical/index-format TECH_DOCS += technical/pack-format TECH_DOCS += technical/pack-heuristics TECH_DOCS += technical/pack-protocol TECH_DOCS += technical/protocol-capabilities TECH_DOCS += technical/protocol-common TECH_DOCS += technical/racy-git TECH_DOCS += technical/send-pack-pipeline TECH_DOCS += technical/shallow TECH_DOCS += technical/signature-format TECH_DOCS += technical/trivial-merge SP_ARTICLES += $(TECH_DOCS) SP_ARTICLES += technical/api-index DOC_HTML += $(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES)) DOC_MAN1 = $(patsubst %.txt,%.1,$(MAN1_TXT)) DOC_MAN5 = $(patsubst %.txt,%.5,$(MAN5_TXT)) DOC_MAN7 = $(patsubst %.txt,%.7,$(MAN7_TXT)) prefix ?= $(HOME) bindir ?= $(prefix)/bin htmldir ?= $(prefix)/share/doc/git-doc infodir ?= $(prefix)/share/info pdfdir ?= $(prefix)/share/doc/git-doc mandir ?= $(prefix)/share/man man1dir = $(mandir)/man1 man5dir = $(mandir)/man5 man7dir = $(mandir)/man7 # DESTDIR = ASCIIDOC = asciidoc ASCIIDOC_EXTRA = ASCIIDOC_HTML = xhtml11 ASCIIDOC_DOCBOOK = docbook ASCIIDOC_CONF = -f asciidoc.conf ASCIIDOC_COMMON = $(ASCIIDOC) $(ASCIIDOC_EXTRA) $(ASCIIDOC_CONF) \ -agit_version=$(GIT_VERSION) TXT_TO_HTML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_HTML) TXT_TO_XML = $(ASCIIDOC_COMMON) -b $(ASCIIDOC_DOCBOOK) MANPAGE_XSL = manpage-normal.xsl XMLTO = xmlto XMLTO_EXTRA = INSTALL ?= install RM ?= rm -f MAN_REPO = ../../git-manpages HTML_REPO = ../../git-htmldocs MAKEINFO = makeinfo INSTALL_INFO = install-info DOCBOOK2X_TEXI = docbook2x-texi DBLATEX = dblatex ASCIIDOC_DBLATEX_DIR = /etc/asciidoc/dblatex ifndef PERL_PATH PERL_PATH = /usr/bin/perl endif -include ../config.mak.autogen -include ../config.mak # # For docbook-xsl ... # -1.68.1, no extra settings are needed? # 1.69.0, set ASCIIDOC_ROFF? # 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP? # 1.71.1, set ASCIIDOC_ROFF? # 1.72.0, set DOCBOOK_XSL_172. # 1.73.0-, no extra settings are needed # ifdef DOCBOOK_XSL_172 ASCIIDOC_EXTRA += -a git-asciidoc-no-roff MANPAGE_XSL = manpage-1.72.xsl else ifndef ASCIIDOC_ROFF # docbook-xsl after 1.72 needs the regular XSL, but will not # pass-thru raw roff codes from asciidoc.conf, so turn them off. ASCIIDOC_EXTRA += -a git-asciidoc-no-roff endif endif ifndef NO_MAN_BOLD_LITERAL XMLTO_EXTRA += -m manpage-bold-literal.xsl endif ifdef DOCBOOK_SUPPRESS_SP XMLTO_EXTRA += -m manpage-suppress-sp.xsl endif # Newer DocBook stylesheet emits warning cruft in the output when # this is not set, and if set it shows an absolute link. Older # stylesheets simply ignore this parameter. # # Distros may want to use MAN_BASE_URL=file:///path/to/git/docs/ # or similar. ifndef MAN_BASE_URL MAN_BASE_URL = file://$(htmldir)/ endif XMLTO_EXTRA += -m manpage-base-url.xsl # If your target system uses GNU groff, it may try to render # apostrophes as a "pretty" apostrophe using unicode. This breaks # cut&paste, so you should set GNU_ROFF to force them to be ASCII # apostrophes. Unfortunately does not work with non-GNU roff. ifdef GNU_ROFF XMLTO_EXTRA += -m manpage-quote-apos.xsl endif SHELL_PATH ?= $(SHELL) # Shell quote; SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) ifdef DEFAULT_PAGER DEFAULT_PAGER_SQ = $(subst ','\'',$(DEFAULT_PAGER)) ASCIIDOC_EXTRA += -a 'git-default-pager=$(DEFAULT_PAGER_SQ)' endif ifdef DEFAULT_EDITOR DEFAULT_EDITOR_SQ = $(subst ','\'',$(DEFAULT_EDITOR)) ASCIIDOC_EXTRA += -a 'git-default-editor=$(DEFAULT_EDITOR_SQ)' endif QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir QUIET_SUBDIR1 = ifneq ($(findstring $(MAKEFLAGS),w),w) PRINT_DIR = --no-print-directory else # "make -w" NO_SUBDIR = : endif ifneq ($(findstring $(MAKEFLAGS),s),s) ifndef V QUIET_ASCIIDOC = @echo ' ' ASCIIDOC $@; QUIET_XMLTO = @echo ' ' XMLTO $@; QUIET_DB2TEXI = @echo ' ' DB2TEXI $@; QUIET_MAKEINFO = @echo ' ' MAKEINFO $@; QUIET_DBLATEX = @echo ' ' DBLATEX $@; QUIET_XSLTPROC = @echo ' ' XSLTPROC $@; QUIET_GEN = @echo ' ' GEN $@; QUIET_LINT = @echo ' ' LINT $@; QUIET_STDERR = 2> /dev/null QUIET_SUBDIR0 = +@subdir= QUIET_SUBDIR1 = ;$(NO_SUBDIR) echo ' ' SUBDIR $$subdir; \ $(MAKE) $(PRINT_DIR) -C $$subdir export V endif endif all: html man html: $(DOC_HTML) man: man1 man5 man7 man1: $(DOC_MAN1) man5: $(DOC_MAN5) man7: $(DOC_MAN7) info: git.info gitman.info pdf: user-manual.pdf install: install-man install-man: man $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir) $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir) $(INSTALL) -d -m 755 $(DESTDIR)$(man7dir) $(INSTALL) -m 644 $(DOC_MAN1) $(DESTDIR)$(man1dir) $(INSTALL) -m 644 $(DOC_MAN5) $(DESTDIR)$(man5dir) $(INSTALL) -m 644 $(DOC_MAN7) $(DESTDIR)$(man7dir) install-info: info $(INSTALL) -d -m 755 $(DESTDIR)$(infodir) $(INSTALL) -m 644 git.info gitman.info $(DESTDIR)$(infodir) if test -r $(DESTDIR)$(infodir)/dir; then \ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) git.info ;\ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) gitman.info ;\ else \ echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \ fi install-pdf: pdf $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir) $(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir) install-html: html '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir) ../GIT-VERSION-FILE: FORCE $(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE -include ../GIT-VERSION-FILE # # Determine "include::" file references in asciidoc files. # docdep_prereqs = \ mergetools-list.made $(mergetools_txt) \ cmd-list.made $(cmds_txt) doc.dep : $(docdep_prereqs) $(wildcard *.txt) build-docdep.perl $(QUIET_GEN)$(RM) $@+ $@ && \ $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \ mv $@+ $@ -include doc.dep cmds_txt = cmds-ancillaryinterrogators.txt \ cmds-ancillarymanipulators.txt \ cmds-mainporcelain.txt \ cmds-plumbinginterrogators.txt \ cmds-plumbingmanipulators.txt \ cmds-synchingrepositories.txt \ cmds-synchelpers.txt \ cmds-purehelpers.txt \ cmds-foreignscminterface.txt $(cmds_txt): cmd-list.made cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT) $(QUIET_GEN)$(RM) $@ && \ $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \ date >$@ mergetools_txt = mergetools-diff.txt mergetools-merge.txt $(mergetools_txt): mergetools-list.made mergetools-list.made: ../git-mergetool--lib.sh $(wildcard ../mergetools/*) $(QUIET_GEN)$(RM) $@ && \ $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \ . ../git-mergetool--lib.sh && \ show_tool_names can_diff "* " || :' >mergetools-diff.txt && \ $(SHELL_PATH) -c 'MERGE_TOOLS_DIR=../mergetools && \ . ../git-mergetool--lib.sh && \ show_tool_names can_merge "* " || :' >mergetools-merge.txt && \ date >$@ clean: $(RM) *.xml *.xml+ *.html *.html+ *.1 *.5 *.7 $(RM) *.texi *.texi+ *.texi++ git.info gitman.info $(RM) *.pdf $(RM) howto-index.txt howto/*.html doc.dep $(RM) technical/*.html technical/api-index.txt $(RM) $(cmds_txt) $(mergetools_txt) *.made $(RM) manpage-base-url.xsl $(MAN_HTML): %.html : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(TXT_TO_HTML) -d manpage -o $@+ $< && \ mv $@+ $@ $(OBSOLETE_HTML): %.html : %.txto asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(TXT_TO_HTML) -o $@+ $< && \ mv $@+ $@ manpage-base-url.xsl: manpage-base-url.xsl.in sed "s|@@MAN_BASE_URL@@|$(MAN_BASE_URL)|" $< > $@ %.1 %.5 %.7 : %.xml manpage-base-url.xsl $(QUIET_XMLTO)$(RM) $@ && \ $(XMLTO) -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $< %.xml : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(TXT_TO_XML) -d manpage -o $@+ $< && \ mv $@+ $@ user-manual.xml: user-manual.txt user-manual.conf $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ $(TXT_TO_XML) -d article -o $@+ $< && \ mv $@+ $@ technical/api-index.txt: technical/api-index-skel.txt \ technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS)) $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh technical/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../ $(patsubst %,%.html,$(API_DOCS) technical/api-index $(TECH_DOCS)): %.html : %.txt asciidoc.conf $(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt XSLT = docbook.xsl XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css user-manual.html: user-manual.xml $(XSLT) $(QUIET_XSLTPROC)$(RM) $@+ $@ && \ xsltproc $(XSLTOPTS) -o $@+ $(XSLT) $< && \ mv $@+ $@ git.info: user-manual.texi $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ user-manual.texi user-manual.texi: user-manual.xml $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ $(DOCBOOK2X_TEXI) user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \ $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \ rm $@++ && \ mv $@+ $@ user-manual.pdf: user-manual.xml $(QUIET_DBLATEX)$(RM) $@+ $@ && \ $(DBLATEX) -o $@+ -p $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.xsl -s $(ASCIIDOC_DBLATEX_DIR)/asciidoc-dblatex.sty $< && \ mv $@+ $@ gitman.texi: $(MAN_XML) cat-texi.perl $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \ --to-stdout $(xml) &&) true) > $@++ && \ $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \ rm $@++ && \ mv $@+ $@ gitman.info: gitman.texi $(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml $(QUIET_DB2TEXI)$(RM) $@+ $@ && \ $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \ mv $@+ $@ howto-index.txt: howto-index.sh $(wildcard howto/*.txt) $(QUIET_GEN)$(RM) $@+ $@ && \ '$(SHELL_PATH_SQ)' ./howto-index.sh $(sort $(wildcard howto/*.txt)) >$@+ && \ mv $@+ $@ $(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt $(QUIET_ASCIIDOC)$(TXT_TO_HTML) $*.txt WEBDOC_DEST = /pub/software/scm/git/docs howto/%.html: ASCIIDOC_EXTRA += -a git-relative-html-prefix=../ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \ sed -e '1,/^$$/d' $< | \ $(TXT_TO_HTML) - >$@+ && \ mv $@+ $@ install-webdoc : html '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST) # You must have a clone of 'git-htmldocs' and 'git-manpages' repositories # next to the 'git' repository itself for the following to work. quick-install: quick-install-man require-manrepo:: @if test ! -d $(MAN_REPO); \ then echo "git-manpages repository must exist at $(MAN_REPO)"; exit 1; fi quick-install-man: require-manrepo '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(MAN_REPO) $(DESTDIR)$(mandir) require-htmlrepo:: @if test ! -d $(HTML_REPO); \ then echo "git-htmldocs repository must exist at $(HTML_REPO)"; exit 1; fi quick-install-html: require-htmlrepo '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REPO) $(DESTDIR)$(htmldir) print-man1: @for i in $(MAN1_TXT); do echo $$i; done lint-docs:: $(QUIET_LINT)$(PERL_PATH) lint-gitlink.perl .PHONY: FORCE cgit-1.1/git/Documentation/RelNotes/000077500000000000000000000000001300467324400174335ustar00rootroot00000000000000cgit-1.1/git/Documentation/RelNotes/1.5.0.1.txt000066400000000000000000000023471300467324400210020ustar00rootroot00000000000000GIT v1.5.0.1 Release Notes ========================== Fixes since v1.5.0 ------------------ * Documentation updates - Clarifications and corrections to 1.5.0 release notes. - The main documentation did not link to git-remote documentation. - Clarified introductory text of git-rebase documentation. - Converted remaining mentions of update-index on Porcelain documents to git-add/git-rm. - Some i18n.* configuration variables were incorrectly described as core.*; fixed. * Bugfixes - git-add and git-update-index on a filesystem on which executable bits are unreliable incorrectly reused st_mode bits even when the path changed between symlink and regular file. - git-daemon marks the listening sockets with FD_CLOEXEC so that it won't be leaked into the children. - segfault from git-blame when the mandatory pathname parameter was missing was fixed; usage() message is given instead. - git-rev-list did not read $GIT_DIR/config file, which means that did not honor i18n.logoutputencoding correctly. * Tweaks - sliding mmap() inefficiently mmaped the same region of a packfile with an access pattern that used objects in the reverse order. This has been made more efficient. cgit-1.1/git/Documentation/RelNotes/1.5.0.2.txt000066400000000000000000000044041300467324400207770ustar00rootroot00000000000000GIT v1.5.0.2 Release Notes ========================== Fixes since v1.5.0.1 -------------------- * Bugfixes - Automated merge conflict handling when changes to symbolic links conflicted were completely broken. The merge-resolve strategy created a regular file with conflict markers in it in place of the symbolic link. The default strategy, merge-recursive was even more broken. It removed the path that was pointed at by the symbolic link. Both of these problems have been fixed. - 'git diff maint master next' did not correctly give combined diff across three trees. - 'git fast-import' portability fix for Solaris. - 'git show-ref --verify' without arguments did not error out but segfaulted. - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra slashes after a/ and b/. - 'git format-patch' produced too long filenames if the commit message had too long line at the beginning. - Running 'make all' and then without changing anything running 'make install' still rebuilt some files. This was inconvenient when building as yourself and then installing as root (especially problematic when the source directory is on NFS and root is mapped to nobody). - 'git-rerere' failed to deal with two unconflicted paths that sorted next to each other. - 'git-rerere' attempted to open(2) a symlink and failed if there was a conflict. Since a conflicting change to a symlink would not benefit from rerere anyway, the command now ignores conflicting changes to symlinks. - 'git-repack' did not like to pass more than 64 arguments internally to underlying 'rev-list' logic, which made it impossible to repack after accumulating many (small) packs in the repository. - 'git-diff' to review the combined diff during a conflicted merge were not reading the working tree version correctly when changes to a symbolic link conflicted. It should have read the data using readlink(2) but read from the regular file the symbolic link pointed at. - 'git-remote' did not like period in a remote's name. * Documentation updates - added and clarified core.bare, core.legacyheaders configurations. - updated "git-clone --depth" documentation. * Assorted git-gui fixes. cgit-1.1/git/Documentation/RelNotes/1.5.0.3.txt000066400000000000000000000030511300467324400207750ustar00rootroot00000000000000GIT v1.5.0.3 Release Notes ========================== Fixes since v1.5.0.2 -------------------- * Bugfixes - 'git.el' honors the commit coding system from the configuration. - 'blameview' in contrib/ correctly digs deeper when a line is clicked. - 'http-push' correctly makes sure the remote side has leading path. Earlier it started in the middle of the path, and incorrectly. - 'git-merge' did not exit with non-zero status when the working tree was dirty and cannot fast forward. It does now. - 'cvsexportcommit' does not lose yet-to-be-used message file. - int-vs-size_t typefix when running combined diff on files over 2GB long. - 'git apply --whitespace=strip' should not touch unmodified lines. - 'git-mailinfo' choke when a logical header line was too long. - 'git show A..B' did not error out. Negative ref ("not A" in this example) does not make sense for the purpose of the command, so now it errors out. - 'git fmt-merge-msg --file' without file parameter did not correctly error out. - 'git archimport' barfed upon encountering a commit without summary. - 'git index-pack' did not protect itself from getting a short read out of pread(2). - 'git http-push' had a few buffer overruns. - Build dependency fixes to rebuild fetch.o when other headers change. * Documentation updates - user-manual updates. - Options to 'git remote add' were described insufficiently. - Configuration format.suffix was not documented. - Other formatting and spelling fixes. cgit-1.1/git/Documentation/RelNotes/1.5.0.4.txt000066400000000000000000000007161300467324400210030ustar00rootroot00000000000000GIT v1.5.0.4 Release Notes ========================== Fixes since v1.5.0.3 -------------------- * Bugfixes - git.el does not add duplicate sign-off lines. - git-commit shows the full stat of the resulting commit, not just about the files in the current directory, when run from a subdirectory. - "git-checkout -m '@{8 hours ago}'" had a funny failure from eval; fixed. - git-gui updates. * Documentation updates * User manual updates cgit-1.1/git/Documentation/RelNotes/1.5.0.5.txt000066400000000000000000000011411300467324400207750ustar00rootroot00000000000000GIT v1.5.0.5 Release Notes ========================== Fixes since v1.5.0.3 -------------------- * Bugfixes - git-merge (hence git-pull) did not refuse fast-forwarding when the working tree had local changes that would have conflicted with it. - git.el does not add duplicate sign-off lines. - git-commit shows the full stat of the resulting commit, not just about the files in the current directory, when run from a subdirectory. - "git-checkout -m '@{8 hours ago}'" had a funny failure from eval; fixed. - git-gui updates. * Documentation updates * User manual updates cgit-1.1/git/Documentation/RelNotes/1.5.0.6.txt000066400000000000000000000007441300467324400210060ustar00rootroot00000000000000GIT v1.5.0.6 Release Notes ========================== Fixes since v1.5.0.5 -------------------- * Bugfixes - a handful small fixes to gitweb. - build procedure for user-manual is fixed not to require locally installed stylesheets. - "git commit $paths" on paths whose earlier contents were already updated in the index were failing out. * Documentation - user-manual has better cross references. - gitweb installation/deployment procedure is now documented. cgit-1.1/git/Documentation/RelNotes/1.5.0.7.txt000066400000000000000000000007421300467324400210050ustar00rootroot00000000000000GIT v1.5.0.7 Release Notes ========================== Fixes since v1.5.0.6 -------------------- * Bugfixes - git-upload-pack failed to close unused pipe ends, resulting in many zombies to hang around. - git-rerere was recording the contents of earlier hunks duplicated in later hunks. This prevented resolving the same conflict when performing the same merge the other way around. * Documentation - a few documentation fixes from Debian package maintainer. cgit-1.1/git/Documentation/RelNotes/1.5.0.txt000066400000000000000000000443161300467324400206450ustar00rootroot00000000000000GIT v1.5.0 Release Notes ======================== Old news -------- This section is for people who are upgrading from ancient versions of git. Although all of the changes in this section happened before the current v1.4.4 release, they are summarized here in the v1.5.0 release notes for people who skipped earlier versions. As of git v1.5.0 there are some optional features that changes the repository to allow data to be stored and transferred more efficiently. These features are not enabled by default, as they will make the repository unusable with older versions of git. Specifically, the available options are: - There is a configuration variable core.legacyheaders that changes the format of loose objects so that they are more efficient to pack and to send out of the repository over git native protocol, since v1.4.2. However, loose objects written in the new format cannot be read by git older than that version; people fetching from your repository using older clients over dumb transports (e.g. http) using older versions of git will also be affected. To let git use the new loose object format, you have to set core.legacyheaders to false. - Since v1.4.3, configuration repack.usedeltabaseoffset allows packfile to be created in more space efficient format, which cannot be read by git older than that version. To let git use the new format for packfiles, you have to set repack.usedeltabaseoffset to true. The above two new features are not enabled by default and you have to explicitly ask for them, because they make repositories unreadable by older versions of git, and in v1.5.0 we still do not enable them by default for the same reason. We will change this default probably 1 year after 1.4.2's release, when it is reasonable to expect everybody to have new enough version of git. - 'git pack-refs' appeared in v1.4.4; this command allows tags to be accessed much more efficiently than the traditional 'one-file-per-tag' format. Older git-native clients can still fetch from a repository that packed and pruned refs (the server side needs to run the up-to-date version of git), but older dumb transports cannot. Packing of refs is done by an explicit user action, either by use of "git pack-refs --prune" command or by use of "git gc" command. - 'git -p' to paginate anything -- many commands do pagination by default on a tty. Introduced between v1.4.1 and v1.4.2; this may surprise old timers. - 'git archive' superseded 'git tar-tree' in v1.4.3; - 'git cvsserver' was new invention in v1.3.0; - 'git repo-config', 'git grep', 'git rebase' and 'gitk' were seriously enhanced during v1.4.0 timeperiod. - 'gitweb' became part of git.git during v1.4.0 timeperiod and seriously modified since then. - reflog is an v1.4.0 invention. This allows you to name a revision that a branch used to be at (e.g. "git diff master@{yesterday} master" allows you to see changes since yesterday's tip of the branch). Updates in v1.5.0 since v1.4.4 series ------------------------------------- * Index manipulation - git-add is to add contents to the index (aka "staging area" for the next commit), whether the file the contents happen to be is an existing one or a newly created one. - git-add without any argument does not add everything anymore. Use 'git-add .' instead. Also you can add otherwise ignored files with an -f option. - git-add tries to be more friendly to users by offering an interactive mode ("git-add -i"). - git-commit used to refuse to commit if was different between HEAD and the index (i.e. update-index was used on it earlier). This check was removed. - git-rm is much saner and safer. It is used to remove paths from both the index file and the working tree, and makes sure you are not losing any local modification before doing so. - git-reset ... can be used to revert index entries for selected paths. - git-update-index is much less visible. Many suggestions to use the command in git output and documentation have now been replaced by simpler commands such as "git add" or "git rm". * Repository layout and objects transfer - The data for origin repository is stored in the configuration file $GIT_DIR/config, not in $GIT_DIR/remotes/, for newly created clones. The latter is still supported and there is no need to convert your existing repository if you are already comfortable with your workflow with the layout. - git-clone always uses what is known as "separate remote" layout for a newly created repository with a working tree. A repository with the separate remote layout starts with only one default branch, 'master', to be used for your own development. Unlike the traditional layout that copied all the upstream branches into your branch namespace (while renaming their 'master' to your 'origin'), the new layout puts upstream branches into local "remote-tracking branches" with their own namespace. These can be referenced with names such as "origin/$upstream_branch_name" and are stored in .git/refs/remotes rather than .git/refs/heads where normal branches are stored. This layout keeps your own branch namespace less cluttered, avoids name collision with your upstream, makes it possible to automatically track new branches created at the remote after you clone from it, and makes it easier to interact with more than one remote repository (you can use "git remote" to add other repositories to track). There might be some surprises: * 'git branch' does not show the remote tracking branches. It only lists your own branches. Use '-r' option to view the tracking branches. * If you are forking off of a branch obtained from the upstream, you would have done something like 'git branch my-next next', because traditional layout dropped the tracking branch 'next' into your own branch namespace. With the separate remote layout, you say 'git branch next origin/next', which allows you to use the matching name 'next' for your own branch. It also allows you to track a remote other than 'origin' (i.e. where you initially cloned from) and fork off of a branch from there the same way (e.g. "git branch mingw j6t/master"). Repositories initialized with the traditional layout continue to work. - New branches that appear on the origin side after a clone is made are also tracked automatically. This is done with an wildcard refspec "refs/heads/*:refs/remotes/origin/*", which older git does not understand, so if you clone with 1.5.0, you would need to downgrade remote.*.fetch in the configuration file to specify each branch you are interested in individually if you plan to fetch into the repository with older versions of git (but why would you?). - Similarly, wildcard refspec "refs/heads/*:refs/remotes/me/*" can be given to "git-push" command to update the tracking branches that is used to track the repository you are pushing from on the remote side. - git-branch and git-show-branch know remote tracking branches (use the command line switch "-r" to list only tracked branches). - git-push can now be used to delete a remote branch or a tag. This requires the updated git on the remote side (use "git push :refs/heads/" to delete "branch"). - git-push more aggressively keeps the transferred objects packed. Earlier we recommended to monitor amount of loose objects and repack regularly, but you should repack when you accumulated too many small packs this way as well. Updated git-count-objects helps you with this. - git-fetch also more aggressively keeps the transferred objects packed. This behavior of git-push and git-fetch can be tweaked with a single configuration transfer.unpacklimit (but usually there should not be any need for a user to tweak it). - A new command, git-remote, can help you manage your remote tracking branch definitions. - You may need to specify explicit paths for upload-pack and/or receive-pack due to your ssh daemon configuration on the other end. This can now be done via remote.*.uploadpack and remote.*.receivepack configuration. * Bare repositories - Certain commands change their behavior in a bare repository (i.e. a repository without associated working tree). We use a fairly conservative heuristic (if $GIT_DIR is ".git", or ends with "/.git", the repository is not bare) to decide if a repository is bare, but "core.bare" configuration variable can be used to override the heuristic when it misidentifies your repository. - git-fetch used to complain updating the current branch but this is now allowed for a bare repository. So is the use of 'git-branch -f' to update the current branch. - Porcelain-ish commands that require a working tree refuses to work in a bare repository. * Reflog - Reflog records the history from the view point of the local repository. In other words, regardless of the real history, the reflog shows the history as seen by one particular repository (this enables you to ask "what was the current revision in _this_ repository, yesterday at 1pm?"). This facility is enabled by default for repositories with working trees, and can be accessed with the "branch@{time}" and "branch@{Nth}" notation. - "git show-branch" learned showing the reflog data with the new -g option. "git log" has -g option to view reflog entries in a more verbose manner. - git-branch knows how to rename branches and moves existing reflog data from the old branch to the new one. - In addition to the reflog support in v1.4.4 series, HEAD reference maintains its own log. "HEAD@{5.minutes.ago}" means the commit you were at 5 minutes ago, which takes branch switching into account. If you want to know where the tip of your current branch was at 5 minutes ago, you need to explicitly say its name (e.g. "master@{5.minutes.ago}") or omit the refname altogether i.e. "@{5.minutes.ago}". - The commits referred to by reflog entries are now protected against pruning. The new command "git reflog expire" can be used to truncate older reflog entries and entries that refer to commits that have been pruned away previously with older versions of git. Existing repositories that have been using reflog may get complaints from fsck-objects and may not be able to run git-repack, if you had run git-prune from older git; please run "git reflog expire --stale-fix --all" first to remove reflog entries that refer to commits that are no longer in the repository when that happens. * Crufts removal - We used to say "old commits are retrievable using reflog and 'master@{yesterday}' syntax as long as you haven't run git-prune". We no longer have to say the latter half of the above sentence, as git-prune does not remove things reachable from reflog entries. - There is a toplevel garbage collector script, 'git-gc', that runs periodic cleanup functions, including 'git-repack -a -d', 'git-reflog expire', 'git-pack-refs --prune', and 'git-rerere gc'. - The output from fsck ("fsck-objects" is called just "fsck" now, but the old name continues to work) was needlessly alarming in that it warned missing objects that are reachable only from dangling objects. This has been corrected and the output is much more useful. * Detached HEAD - You can use 'git-checkout' to check out an arbitrary revision or a tag as well, instead of named branches. This will dissociate your HEAD from the branch you are currently on. A typical use of this feature is to "look around". E.g. $ git checkout v2.6.16 ... compile, test, etc. $ git checkout v2.6.17 ... compile, test, etc. - After detaching your HEAD, you can go back to an existing branch with usual "git checkout $branch". Also you can start a new branch using "git checkout -b $newbranch" to start a new branch at that commit. - You can even pull from other repositories, make merges and commits while your HEAD is detached. Also you can use "git reset" to jump to arbitrary commit, while still keeping your HEAD detached. Remember that a detached state is volatile, i.e. it will be forgotten as soon as you move away from it with the checkout or reset command, unless a branch is created from it as mentioned above. It is also possible to rescue a lost detached state from the HEAD reflog. * Packed refs - Repositories with hundreds of tags have been paying large overhead, both in storage and in runtime, due to the traditional one-ref-per-file format. A new command, git-pack-refs, can be used to "pack" them in more efficient representation (you can let git-gc do this for you). - Clones and fetches over dumb transports are now aware of packed refs and can download from repositories that use them. * Configuration - configuration related to color setting are consolidated under color.* namespace (older diff.color.*, status.color.* are still supported). - 'git-repo-config' command is accessible as 'git-config' now. * Updated features - git-describe uses better criteria to pick a base ref. It used to pick the one with the newest timestamp, but now it picks the one that is topologically the closest (that is, among ancestors of commit C, the ref T that has the shortest output from "git-rev-list T..C" is chosen). - git-describe gives the number of commits since the base ref between the refname and the hash suffix. E.g. the commit one before v2.6.20-rc6 in the kernel repository is: v2.6.20-rc5-306-ga21b069 which tells you that its object name begins with a21b069, v2.6.20-rc5 is an ancestor of it (meaning, the commit contains everything -rc5 has), and there are 306 commits since v2.6.20-rc5. - git-describe with --abbrev=0 can be used to show only the name of the base ref. - git-blame learned a new option, --incremental, that tells it to output the blames as they are assigned. A sample script to use it is also included as contrib/blameview. - git-blame starts annotating from the working tree by default. * Less external dependency - We no longer require the "merge" program from the RCS suite. All 3-way file-level merges are now done internally. - The original implementation of git-merge-recursive which was in Python has been removed; we have a C implementation of it now. - git-shortlog is no longer a Perl script. It no longer requires output piped from git-log; it can accept revision parameters directly on the command line. * I18n - We have always encouraged the commit message to be encoded in UTF-8, but the users are allowed to use legacy encoding as appropriate for their projects. This will continue to be the case. However, a non UTF-8 commit encoding _must_ be explicitly set with i18n.commitencoding in the repository where a commit is made; otherwise git-commit-tree will complain if the log message does not look like a valid UTF-8 string. - The value of i18n.commitencoding in the originating repository is recorded in the commit object on the "encoding" header, if it is not UTF-8. git-log and friends notice this, and reencodes the message to the log output encoding when displaying, if they are different. The log output encoding is determined by "git log --encoding=", i18n.logoutputencoding configuration, or i18n.commitencoding configuration, in the decreasing order of preference, and defaults to UTF-8. - Tools for e-mailed patch application now default to -u behavior; i.e. it always re-codes from the e-mailed encoding to the encoding specified with i18n.commitencoding. This unfortunately forces projects that have happily been using a legacy encoding without setting i18n.commitencoding to set the configuration, but taken with other improvement, please excuse us for this very minor one-time inconvenience. * e-mailed patches - See the above I18n section. - git-format-patch now enables --binary without being asked. git-am does _not_ default to it, as sending binary patch via e-mail is unusual and is harder to review than textual patches and it is prudent to require the person who is applying the patch to explicitly ask for it. - The default suffix for git-format-patch output is now ".patch", not ".txt". This can be changed with --suffix=.txt option, or setting the config variable "format.suffix" to ".txt". * Foreign SCM interfaces - git-svn now requires the Perl SVN:: libraries, the command-line backend was too slow and limited. - the 'commit' subcommand of git-svn has been renamed to 'set-tree', and 'dcommit' is the recommended replacement for day-to-day work. - git fast-import backend. * User support - Quite a lot of documentation updates. - Bash completion scripts have been updated heavily. - Better error messages for often used Porcelainish commands. - Git GUI. This is a simple Tk based graphical interface for common Git operations. * Sliding mmap - We used to assume that we can mmap the whole packfile while in use, but with a large project this consumes huge virtual memory space and truly huge ones would not fit in the userland address space on 32-bit platforms. We now mmap huge packfile in pieces to avoid this problem. * Shallow clones - There is a partial support for 'shallow' repositories that keeps only recent history. A 'shallow clone' is created by specifying how deep that truncated history should be (e.g. "git clone --depth 5 git://some.where/repo.git"). Currently a shallow repository has number of limitations: - Cloning and fetching _from_ a shallow clone are not supported (nor tested -- so they might work by accident but they are not expected to). - Pushing from nor into a shallow clone are not expected to work. - Merging inside a shallow repository would work as long as a merge base is found in the recent history, but otherwise it will be like merging unrelated histories and may result in huge conflicts. but this would be more than adequate for people who want to look at near the tip of a big project with a deep history and send patches in e-mail format. cgit-1.1/git/Documentation/RelNotes/1.5.1.1.txt000066400000000000000000000041641300467324400210020ustar00rootroot00000000000000GIT v1.5.1.1 Release Notes ========================== Fixes since v1.5.1 ------------------ * Documentation updates - The --left-right option of rev-list and friends is documented. - The documentation for cvsimport has been majorly improved. - "git-show-ref --exclude-existing" was documented. * Bugfixes - The implementation of -p option in "git cvsexportcommit" had the meaning of -C (context reduction) option wrong, and loosened the context requirements when it was told to be strict. - "git cvsserver" did not behave like the real cvsserver when client side removed a file from the working tree without doing anything else on the path. In such a case, it should restore it from the checked out revision. - "git fsck" issued an alarming error message on detached HEAD. It is not an error since at least 1.5.0. - "git send-email" produced of References header of unbounded length; fixed this with line-folding. - "git archive" to download from remote site should not require you to be in a git repository, but it incorrectly did. - "git apply" ignored -p for "diff --git" formatted patches. - "git rerere" recorded a conflict that had one side empty (the other side adds) incorrectly; this made merging in the other direction fail to use previously recorded resolution. - t4200 test was broken where "wc -l" pads its output with spaces. - "git branch -m old new" to rename branch did not work without a configuration file in ".git/config". - The sample hook for notification e-mail was misnamed. - gitweb did not show type-changing patch correctly in the blobdiff view. - git-svn did not error out with incorrect command line options. - git-svn fell into an infinite loop when insanely long commit message was found. - git-svn dcommit and rebase was confused by patches that were merged from another branch that is managed by git-svn. - git-svn used to get confused when globbing remote branch/tag spec (e.g. "branches = proj/branches/*:refs/remotes/origin/*") is used and there was a plain file that matched the glob. cgit-1.1/git/Documentation/RelNotes/1.5.1.2.txt000066400000000000000000000031631300467324400210010ustar00rootroot00000000000000GIT v1.5.1.2 Release Notes ========================== Fixes since v1.5.1.1 -------------------- * Bugfixes - "git clone" over http from a repository that has lost the loose refs by running "git pack-refs" were broken (a code to deal with this was added to "git fetch" in v1.5.0, but it was missing from "git clone"). - "git diff a/ b/" incorrectly fell in "diff between two filesystem objects" codepath, when the user most likely wanted to limit the extent of output to two tracked directories. - git-quiltimport had the same bug as we fixed for git-applymbox in v1.5.1.1 -- it gave an alarming "did not have any patch" message (but did not actually fail and was harmless). - various git-svn fixes. - Sample update hook incorrectly always refused requests to delete branches through push. - git-blame on a very long working tree path had buffer overrun problem. - git-apply did not like to be fed two patches in a row that created and then modified the same file. - git-svn was confused when a non-project was stored directly under trunk/, branches/ and tags/. - git-svn wants the Error.pm module that was at least as new as what we ship as part of git; install ours in our private installation location if the one on the system is older. - An earlier update to command line integer parameter parser was botched and made 'update-index --cacheinfo' completely useless. * Documentation updates - Various documentation updates from J. Bruce Fields, Frank Lichtenheld, Alex Riesen and others. Andrew Ruder started a war on undocumented options. cgit-1.1/git/Documentation/RelNotes/1.5.1.3.txt000066400000000000000000000030361300467324400210010ustar00rootroot00000000000000GIT v1.5.1.3 Release Notes ========================== Fixes since v1.5.1.2 -------------------- * Bugfixes - git-add tried to optimize by finding common leading directories across its arguments but botched, causing very confused behaviour. - unofficial rpm.spec file shipped with git was letting ETC_GITCONFIG set to /usr/etc/gitconfig. Tweak the official Makefile to make it harder for distro people to make the same mistake, by setting the variable to /etc/gitconfig if prefix is set to /usr. - git-svn inconsistently stripped away username from the URL only when svnsync_props was in use. - git-svn got confused when handling symlinks on Mac OS. - git-send-email was not quoting recipient names that have period '.' in them. Also it did not allow overriding envelope sender, which made it impossible to send patches to certain subscriber-only lists. - built-in write_tree() routine had a sequence that renamed a file that is still open, which some systems did not like. - when memory is very tight, sliding mmap code to read packfiles incorrectly closed the fd that was still being used to read the pack. - import-tars contributed front-end for fastimport was passing wrong directory modes without checking. - git-fastimport trusted its input too much and allowed to create corrupt tree objects with entries without a name. - git-fetch needlessly barfed when too long reflog action description was given by the caller. Also contains various documentation updates. cgit-1.1/git/Documentation/RelNotes/1.5.1.4.txt000066400000000000000000000015131300467324400210000ustar00rootroot00000000000000GIT v1.5.1.4 Release Notes ========================== Fixes since v1.5.1.3 -------------------- * Bugfixes - "git-http-fetch" did not work around a bug in libcurl earlier than 7.16 (curl_multi_remove_handle() was broken). - "git cvsserver" handles a file that was once removed and then added again correctly. - import-tars script (in contrib/) handles GNU tar archives that contain pathnames longer than 100 bytes (long-link extension) correctly. - xdelta test program did not build correctly. - gitweb sometimes tried incorrectly to apply function to decode utf8 twice, resulting in corrupt output. - "git blame -C" mishandled text at the end of a group of lines. - "git log/rev-list --boundary" did not produce output correctly without --left-right option. - Many documentation updates. cgit-1.1/git/Documentation/RelNotes/1.5.1.5.txt000066400000000000000000000026261300467324400210070ustar00rootroot00000000000000GIT v1.5.1.5 Release Notes ========================== Fixes since v1.5.1.4 -------------------- * Bugfixes - git-send-email did not understand aliases file for mutt, which allows leading whitespaces. - git-format-patch emitted Content-Type and Content-Transfer-Encoding headers for non ASCII contents, but failed to add MIME-Version. - git-name-rev had a buffer overrun with a deep history. - contributed script import-tars did not get the directory in tar archives interpreted correctly. - git-svn was reported to segfault for many people on list and #git; hopefully this has been fixed. - "git-svn clone" does not try to minimize the URL (i.e. connect to higher level hierarchy) by default, as this can prevent clone to fail if only part of the repository (e.g. 'trunk') is open to public. - "git checkout branch^0" did not detach the head when you are already on 'branch'; backported the fix from the 'master'. - "git-config section.var" did not correctly work when existing configuration file had both [section] and [section "name"] next to each other. - "git clone ../other-directory" was fooled if the current directory $PWD points at is a symbolic link. - (build) tree_entry_extract() function was both static inline and extern, which caused trouble compiling with Forte12 compilers on Sun. - Many many documentation fixes and updates. cgit-1.1/git/Documentation/RelNotes/1.5.1.6.txt000066400000000000000000000027571300467324400210150ustar00rootroot00000000000000GIT v1.5.1.6 Release Notes ========================== Fixes since v1.5.1.4 -------------------- * Bugfixes - git-send-email did not understand aliases file for mutt, which allows leading whitespaces. - git-format-patch emitted Content-Type and Content-Transfer-Encoding headers for non ASCII contents, but failed to add MIME-Version. - git-name-rev had a buffer overrun with a deep history. - contributed script import-tars did not get the directory in tar archives interpreted correctly. - git-svn was reported to segfault for many people on list and #git; hopefully this has been fixed. - git-svn also had a bug to crash svnserve by sending a bad sequence of requests. - "git-svn clone" does not try to minimize the URL (i.e. connect to higher level hierarchy) by default, as this can prevent clone to fail if only part of the repository (e.g. 'trunk') is open to public. - "git checkout branch^0" did not detach the head when you are already on 'branch'; backported the fix from the 'master'. - "git-config section.var" did not correctly work when existing configuration file had both [section] and [section "name"] next to each other. - "git clone ../other-directory" was fooled if the current directory $PWD points at is a symbolic link. - (build) tree_entry_extract() function was both static inline and extern, which caused trouble compiling with Forte12 compilers on Sun. - Many many documentation fixes and updates. cgit-1.1/git/Documentation/RelNotes/1.5.1.txt000066400000000000000000000317501300467324400206440ustar00rootroot00000000000000GIT v1.5.1 Release Notes ======================== Updates since v1.5.0 -------------------- * Deprecated commands and options. - git-diff-stages and git-resolve have been removed. * New commands and options. - "git log" and friends take --reverse, which instructs them to give their output in the order opposite from their usual. They typically output from new to old, but with this option their output would read from old to new. "git shortlog" usually lists older commits first, but with this option, they are shown from new to old. - "git log --pretty=format:" to allow more flexible custom log output. - "git diff" learned --ignore-space-at-eol. This is a weaker form of --ignore-space-change. - "git diff --no-index pathA pathB" can be used as diff replacement with git specific enhancements. - "git diff --no-index" can read from '-' (standard input). - "git diff" also learned --exit-code to exit with non-zero status when it found differences. In the future we might want to make this the default but that would be a rather big backward incompatible change; it will stay as an option for now. - "git diff --quiet" is --exit-code with output turned off, meant for scripted use to quickly determine if there is any tree-level difference. - Textual patch generation with "git diff" without -w/-b option has been significantly optimized. "git blame" got faster because of the same change. - "git log" and "git rev-list" has been optimized significantly when they are used with pathspecs. - "git branch --track" can be used to set up configuration variables to help it easier to base your work on branches you track from a remote site. - "git format-patch --attach" now emits attachments. Use --inline to get an inlined multipart/mixed. - "git name-rev" learned --refs=, to limit the tags used for naming the given revisions only to the ones matching the given pattern. - "git remote update" is to run "git fetch" for defined remotes to update tracking branches. - "git cvsimport" can now take '-d' to talk with a CVS repository different from what are recorded in CVS/Root (overriding it with environment CVSROOT does not work). - "git bundle" can help sneaker-netting your changes between repositories. - "git mergetool" can help 3-way file-level conflict resolution with your favorite graphical merge tools. - A new configuration "core.symlinks" can be used to disable symlinks on filesystems that do not support them; they are checked out as regular files instead. - You can name a commit object with its first line of the message. The syntax to use is ':/message text'. E.g. $ git show ":/object name: introduce ':/' notation" means the same thing as: $ git show 28a4d940443806412effa246ecc7768a21553ec7 - "git bisect" learned a new command "run" that takes a script to run after each revision is checked out to determine if it is good or bad, to automate the bisection process. - "git log" family learned a new traversal option --first-parent, which does what the name suggests. * Updated behavior of existing commands. - "git-merge-recursive" used to barf when there are more than one common ancestors for the merge, and merging them had a rename/rename conflict. This has been fixed. - "git fsck" does not barf on corrupt loose objects. - "git rm" does not remove newly added files without -f. - "git archimport" allows remapping when coming up with git branch names from arch names. - git-svn got almost a rewrite. - core.autocrlf configuration, when set to 'true', makes git to convert CRLF at the end of lines in text files to LF when reading from the filesystem, and convert in reverse when writing to the filesystem. The variable can be set to 'input', in which case the conversion happens only while reading from the filesystem but files are written out with LF at the end of lines. Currently, which paths to consider 'text' (i.e. be subjected to the autocrlf mechanism) is decided purely based on the contents, but the plan is to allow users to explicitly override this heuristic based on paths. - The behavior of 'git-apply', when run in a subdirectory, without --index nor --cached were inconsistent with that of the command with these options. This was fixed to match the behavior with --index. A patch that is meant to be applied with -p1 from the toplevel of the project tree can be applied with any custom -p option. A patch that is not relative to the toplevel needs to be applied with -p option with or without --index (or --cached). - "git diff" outputs a trailing HT when pathnames have embedded SP on +++/--- header lines, in order to help "GNU patch" to parse its output. "git apply" was already updated to accept this modified output format since ce74618d (Sep 22, 2006). - "git cvsserver" runs hooks/update and honors its exit status. - "git cvsserver" can be told to send everything with -kb. - "git diff --check" also honors the --color output option. - "git name-rev" used to stress the fact that a ref is a tag too much, by saying something like "v1.2.3^0~22". It now says "v1.2.3~22" in such a case (it still says "v1.2.3^0" if it does not talk about an ancestor of the commit that is tagged, which makes sense). - "git rev-list --boundary" now shows boundary markers for the commits omitted by --max-age and --max-count condition. - The configuration mechanism now reads $(prefix)/etc/gitconfig. - "git apply --verbose" shows what preimage lines were wanted when it couldn't find them. - "git status" in a read-only repository got a bit saner. - "git fetch" (hence "git clone" and "git pull") are less noisy when the output does not go to tty. - "git fetch" between repositories with many refs were slow even when there are not many changes that needed transferring. This has been sped up by partially rewriting the heaviest parts in C. - "git mailinfo" which splits an e-mail into a patch and the meta-information was rewritten, thanks to Don Zickus. It handles nested multipart better. The command was broken for a brief period on 'master' branch since 1.5.0 but the breakage is fixed now. - send-email learned configurable bcc and chain-reply-to. - "git remote show $remote" also talks about branches that would be pushed if you run "git push remote". - Using objects from packs is now seriously optimized by clever use of a cache. This should be most noticeable in git-log family of commands that involve reading many tree objects. In addition, traversing revisions while filtering changes with pathspecs is made faster by terminating the comparison between the trees as early as possible. * Hooks - The part to send out notification e-mails was removed from the sample update hook, as it was not an appropriate place to do so. The proper place to do this is the new post-receive hook. An example hook has been added to contrib/hooks/. * Others - git-revert, git-gc and git-cherry-pick are now built-ins. Fixes since v1.5.0 ------------------ These are all in v1.5.0.x series. * Documentation updates - Clarifications and corrections to 1.5.0 release notes. - The main documentation did not link to git-remote documentation. - Clarified introductory text of git-rebase documentation. - Converted remaining mentions of update-index on Porcelain documents to git-add/git-rm. - Some i18n.* configuration variables were incorrectly described as core.*; fixed. - added and clarified core.bare, core.legacyheaders configurations. - updated "git-clone --depth" documentation. - user-manual updates. - Options to 'git remote add' were described insufficiently. - Configuration format.suffix was not documented. - Other formatting and spelling fixes. - user-manual has better cross references. - gitweb installation/deployment procedure is now documented. * Bugfixes - git-upload-pack closes unused pipe ends; earlier this caused many zombies to hang around. - git-rerere was recording the contents of earlier hunks duplicated in later hunks. This prevented resolving the same conflict when performing the same merge the other way around. - git-add and git-update-index on a filesystem on which executable bits are unreliable incorrectly reused st_mode bits even when the path changed between symlink and regular file. - git-daemon marks the listening sockets with FD_CLOEXEC so that it won't be leaked into the children. - segfault from git-blame when the mandatory pathname parameter was missing was fixed; usage() message is given instead. - git-rev-list did not read $GIT_DIR/config file, which means that did not honor i18n.logoutputencoding correctly. - Automated merge conflict handling when changes to symbolic links conflicted were completely broken. The merge-resolve strategy created a regular file with conflict markers in it in place of the symbolic link. The default strategy, merge-recursive was even more broken. It removed the path that was pointed at by the symbolic link. Both of these problems have been fixed. - 'git diff maint master next' did not correctly give combined diff across three trees. - 'git fast-import' portability fix for Solaris. - 'git show-ref --verify' without arguments did not error out but segfaulted. - 'git diff :tracked-file `pwd`/an-untracked-file' gave an extra slashes after a/ and b/. - 'git format-patch' produced too long filenames if the commit message had too long line at the beginning. - Running 'make all' and then without changing anything running 'make install' still rebuilt some files. This was inconvenient when building as yourself and then installing as root (especially problematic when the source directory is on NFS and root is mapped to nobody). - 'git-rerere' failed to deal with two unconflicted paths that sorted next to each other. - 'git-rerere' attempted to open(2) a symlink and failed if there was a conflict. Since a conflicting change to a symlink would not benefit from rerere anyway, the command now ignores conflicting changes to symlinks. - 'git-repack' did not like to pass more than 64 arguments internally to underlying 'rev-list' logic, which made it impossible to repack after accumulating many (small) packs in the repository. - 'git-diff' to review the combined diff during a conflicted merge were not reading the working tree version correctly when changes to a symbolic link conflicted. It should have read the data using readlink(2) but read from the regular file the symbolic link pointed at. - 'git-remote' did not like period in a remote's name. - 'git.el' honors the commit coding system from the configuration. - 'blameview' in contrib/ correctly digs deeper when a line is clicked. - 'http-push' correctly makes sure the remote side has leading path. Earlier it started in the middle of the path, and incorrectly. - 'git-merge' did not exit with non-zero status when the working tree was dirty and cannot fast forward. It does now. - 'cvsexportcommit' does not lose yet-to-be-used message file. - int-vs-size_t typefix when running combined diff on files over 2GB long. - 'git apply --whitespace=strip' should not touch unmodified lines. - 'git-mailinfo' choke when a logical header line was too long. - 'git show A..B' did not error out. Negative ref ("not A" in this example) does not make sense for the purpose of the command, so now it errors out. - 'git fmt-merge-msg --file' without file parameter did not correctly error out. - 'git archimport' barfed upon encountering a commit without summary. - 'git index-pack' did not protect itself from getting a short read out of pread(2). - 'git http-push' had a few buffer overruns. - Build dependency fixes to rebuild fetch.o when other headers change. - git.el does not add duplicate sign-off lines. - git-commit shows the full stat of the resulting commit, not just about the files in the current directory, when run from a subdirectory. - "git-checkout -m '@{8 hours ago}'" had a funny failure from eval; fixed. - git-merge (hence git-pull) did not refuse fast-forwarding when the working tree had local changes that would have conflicted with it. - a handful small fixes to gitweb. - build procedure for user-manual is fixed not to require locally installed stylesheets. - "git commit $paths" on paths whose earlier contents were already updated in the index were failing out. * Tweaks - sliding mmap() inefficiently mmaped the same region of a packfile with an access pattern that used objects in the reverse order. This has been made more efficient. cgit-1.1/git/Documentation/RelNotes/1.5.2.1.txt000066400000000000000000000026611300467324400210030ustar00rootroot00000000000000GIT v1.5.2.1 Release Notes ========================== Fixes since v1.5.2 ------------------ * Bugfixes - Temporary files that are used when invoking external diff programs did not tolerate a long TMPDIR. - git-daemon did not notice when it could not write into its pid file. - git-status did not honor core.excludesFile configuration like git-add did. - git-annotate did not work from a subdirectory while git-blame did. - git-cvsserver should have disabled access to a repository with "gitcvs.pserver.enabled = false" set even when "gitcvs.enabled = true" was set at the same time. It didn't. - git-cvsimport did not work correctly in a repository with its branch heads were packed with pack-refs. - ident unexpansion to squash "$Id: xxx $" that is in the repository copy removed incorrect number of bytes. - git-svn misbehaved when the subversion repository did not provide MD5 checksums for files. - git rebase (and git am) misbehaved on commits that have '\n' (literally backslash and en, not a linefeed) in the title. - code to decode base85 used in binary patches had one error return codepath wrong. - RFC2047 Q encoding output by git-format-patch used '_' for a space, which is not understood by some programs. It uses =20 which is safer. - git-fastimport --import-marks was broken; fixed. - A lot of documentation updates, clarifications and fixes. cgit-1.1/git/Documentation/RelNotes/1.5.2.2.txt000066400000000000000000000040461300467324400210030ustar00rootroot00000000000000GIT v1.5.2.2 Release Notes ========================== Fixes since v1.5.2.1 -------------------- * Usability fix - git-gui is shipped with its updated blame interface. It is rumored that the older one was not just unusable but was active health hazard, but this one is actually pretty. Please see for yourself. * Bugfixes - "git checkout fubar" was utterly confused when there is a branch fubar and a tag fubar at the same time. It correctly checks out the branch fubar now. - "git clone /path/foo" to clone a local /path/foo.git repository left an incorrect configuration. - "git send-email" correctly unquotes RFC 2047 quoted names in the patch-email before using their values. - We did not accept number of seconds since epoch older than year 2000 as a valid timestamp. We now interpret positive integers more than 8 digits as such, which allows us to express timestamps more recent than March 1973. - git-cvsimport did not work when you have GIT_DIR to point your repository at a nonstandard location. - Some systems (notably, Solaris) lack hstrerror() to make h_errno human readable; prepare a replacement implementation. - .gitignore file listed git-core.spec but what we generate is git.spec, and nobody noticed for a long time. - "git-merge-recursive" does not try to run file level merge on binary files. - "git-branch --track" did not create tracking configuration correctly when the branch name had slash in it. - The email address of the user specified with user.email configuration was overridden by EMAIL environment variable. - The tree parser did not warn about tree entries with nonsense file modes, and assumed they must be blobs. - "git log -z" without any other request to generate diff still invoked the diff machinery, wasting cycles. * Documentation - Many updates to fix stale or missing documentation. - Although our documentation was primarily meant to be formatted with AsciiDoc7, formatting with AsciiDoc8 is supported better. cgit-1.1/git/Documentation/RelNotes/1.5.2.3.txt000066400000000000000000000014341300467324400210020ustar00rootroot00000000000000GIT v1.5.2.3 Release Notes ========================== Fixes since v1.5.2.2 -------------------- * Bugfixes - Version 2 pack index format was introduced in version 1.5.2 to support pack files that has offset that cannot be represented in 32-bit. The runtime code to validate such an index mishandled such an index for an empty pack. - Commit walkers (most notably, fetch over http protocol) tried to traverse commit objects contained in trees (aka subproject); they shouldn't. - A build option NO_R_TO_GCC_LINKER was not explained in Makefile comment correctly. * Documentation Fixes and Updates - git-config --regexp was not documented properly. - git-repack -a was not documented properly. - git-remote -n was not documented properly. cgit-1.1/git/Documentation/RelNotes/1.5.2.4.txt000066400000000000000000000014421300467324400210020ustar00rootroot00000000000000GIT v1.5.2.4 Release Notes ========================== Fixes since v1.5.2.3 -------------------- * Bugfixes - "git-gui" bugfixes, including a handful fixes to run it better on Cygwin/MSYS. - "git checkout" failed to switch back and forth between branches, one of which has "frotz -> xyzzy" symlink and file "xyzzy/filfre", while the other one has a file "frotz/filfre". - "git prune" used to segfault upon seeing a commit that is referred to by a tree object (aka "subproject"). - "git diff --name-status --no-index" mishandled an added file. - "git apply --reverse --whitespace=warn" still complained about whitespaces that a forward application would have introduced. * Documentation Fixes and Updates - A handful documentation updates. cgit-1.1/git/Documentation/RelNotes/1.5.2.5.txt000066400000000000000000000016401300467324400210030ustar00rootroot00000000000000GIT v1.5.2.5 Release Notes ========================== Fixes since v1.5.2.4 -------------------- * Bugfixes - "git add -u" had a serious data corruption problem in one special case (when the changes to a subdirectory's files consist only deletion of files). - "git add -u " did not work from a subdirectory. - "git apply" left an empty directory after all its files are renamed away. - "git $anycmd foo/bar", when there is a file 'foo' in the working tree, complained that "git $anycmd foo/bar --" form should be used to disambiguate between revs and files, which was completely bogus. - "git checkout-index" and other commands that checks out files to the work tree tried unlink(2) on directories, which is a sane thing to do on sane systems, but not on Solaris when you are root. * Documentation Fixes and Updates - A handful documentation fixes. cgit-1.1/git/Documentation/RelNotes/1.5.2.txt000066400000000000000000000162731300467324400206500ustar00rootroot00000000000000GIT v1.5.2 Release Notes ======================== Updates since v1.5.1 -------------------- * Plumbing level superproject support. You can include a subdirectory that has an independent git repository in your index and tree objects of your project ("superproject"). This plumbing (i.e. "core") level superproject support explicitly excludes recursive behaviour. The "subproject" entries in the index and trees of a superproject are incompatible with older versions of git. Experimenting with the plumbing level support is encouraged, but be warned that unless everybody in your project updates to this release or later, using this feature would make your project inaccessible by people with older versions of git. * Plumbing level gitattributes support. The gitattributes mechanism allows you to add 'attributes' to paths in your project, and affect the way certain git operations work. Currently you can influence if a path is considered a binary or text (the former would be treated by 'git diff' not to produce textual output; the latter can go through the line endings conversion process in repositories with core.autocrlf set), expand and unexpand '$Id$' keyword with blob object name, specify a custom 3-way merge driver, and specify a custom diff driver. You can also apply arbitrary filter to contents on check-in/check-out codepath but this feature is an extremely sharp-edged razor and needs to be handled with caution (do not use it unless you understand the earlier mailing list discussion on keyword expansion). These conversions apply when checking files in or out, and exporting via git-archive. * The packfile format now optionally supports 64-bit index. This release supports the "version 2" format of the .idx file. This is automatically enabled when a huge packfile needs more than 32-bit to express offsets of objects in the pack. * Comes with an updated git-gui 0.7.1 * Updated gitweb: - can show combined diff for merges; - uses font size of user's preference, not hardcoded in pixels; - can now 'grep'; * New commands and options. - "git bisect start" can optionally take a single bad commit and zero or more good commits on the command line. - "git shortlog" can optionally be told to wrap its output. - "subtree" merge strategy allows another project to be merged in as your subdirectory. - "git format-patch" learned a new --subject-prefix= option, to override the built-in "[PATCH]". - "git add -u" is a quick way to do the first stage of "git commit -a" (i.e. update the index to match the working tree); it obviously does not make a commit. - "git clean" honors a new configuration, "clean.requireforce". When set to true, this makes "git clean" a no-op, preventing you from losing files by typing "git clean" when you meant to say "make clean". You can still say "git clean -f" to override this. - "git log" family of commands learned --date={local,relative,default} option. --date=relative is synonym to the --relative-date. --date=local gives the timestamp in local timezone. * Updated behavior of existing commands. - When $GIT_COMMITTER_EMAIL or $GIT_AUTHOR_EMAIL is not set but $EMAIL is set, the latter is used as a substitute. - "git diff --stat" shows size of preimage and postimage blobs for binary contents. Earlier it only said "Bin". - "git lost-found" shows stuff that are unreachable except from reflogs. - "git checkout branch^0" now detaches HEAD at the tip commit on the named branch, instead of just switching to the branch (use "git checkout branch" to switch to the branch, as before). - "git bisect next" can be used after giving only a bad commit without giving a good one (this starts bisection half-way to the root commit). We used to refuse to operate without a good and a bad commit. - "git push", when pushing into more than one repository, does not stop at the first error. - "git archive" does not insist you to give --format parameter anymore; it defaults to "tar". - "git cvsserver" can use backends other than sqlite. - "gitview" (in contrib/ section) learned to better support "git-annotate". - "git diff $commit1:$path2 $commit2:$path2" can now report mode changes between the two blobs. - Local "git fetch" from a repository whose object store is one of the alternates (e.g. fetching from the origin in a repository created with "git clone -l -s") avoids downloading objects unnecessarily. - "git blame" uses .mailmap to canonicalize the author name just like "git shortlog" does. - "git pack-objects" pays attention to pack.depth configuration variable. - "git cherry-pick" and "git revert" does not use .msg file in the working tree to prepare commit message; instead it uses $GIT_DIR/MERGE_MSG as other commands do. * Builds - git-p4import has never been installed; now there is an installation option to do so. - gitk and git-gui can be configured out. - Generated documentation pages automatically get version information from GIT_VERSION. - Parallel build with "make -j" descending into subdirectory was fixed. * Performance Tweaks - Optimized "git-rev-list --bisect" (hence "git-bisect"). - Optimized "git-add $path" in a large directory, most of whose contents are ignored. - Optimized "git-diff-tree" for reduced memory footprint. - The recursive merge strategy updated a worktree file that was changed identically in two branches, when one of them renamed it. We do not do that when there is no rename, so match that behaviour. This avoids excessive rebuilds. - The default pack depth has been increased to 50, as the recent addition of delta_base_cache makes deeper delta chains much less expensive to access. Depending on the project, it was reported that this reduces the resulting pack file by 10% or so. Fixes since v1.5.1 ------------------ All of the fixes in v1.5.1 maintenance series are included in this release, unless otherwise noted. * Bugfixes - Switching branches with "git checkout" refused to work when a path changes from a file to a directory between the current branch and the new branch, in order not to lose possible local changes in the directory that is being turned into a file with the switch. We now allow such a branch switch after making sure that there is no locally modified file nor un-ignored file in the directory. This has not been backported to 1.5.1.x series, as it is rather an intrusive change. - Merging branches that have a file in one and a directory in another at the same path used to get quite confused. We handle such a case a bit more carefully, even though that is still left as a conflict for the user to sort out. This will not be backported to 1.5.1.x series, as it is rather an intrusive change. - git-fetch had trouble with a remote with insanely large number of refs. - "git clean -d -X" now does not remove non-excluded directories. - rebasing (without -m) a series that changes a symlink to a directory in the middle of a path confused git-apply greatly and refused to operate. cgit-1.1/git/Documentation/RelNotes/1.5.3.1.txt000066400000000000000000000005061300467324400210000ustar00rootroot00000000000000GIT v1.5.3.1 Release Notes ========================== Fixes since v1.5.3 ------------------ This is solely to fix the generated RPM's dependencies. We used to have git-p4 package but we do not anymore. As suggested on the mailing list, this release makes git-core "Obsolete" git-p4, so that yum update would not complain. cgit-1.1/git/Documentation/RelNotes/1.5.3.2.txt000066400000000000000000000036131300467324400210030ustar00rootroot00000000000000GIT v1.5.3.2 Release Notes ========================== Fixes since v1.5.3.1 -------------------- * git-push sent thin packs by default, which was not good for the public distribution server (no point in saving transfer while pushing; no point in making the resulting pack less optimum). * git-svn sometimes terminated with "Malformed network data" when talking over svn:// protocol. * git-send-email re-issued the same message-id about 10% of the time if you fired off 30 messages within a single second. * git-stash was not terminating the log message of commits it internally creates with LF. * git-apply failed to check the size of the patch hunk when its beginning part matched the remainder of the preimage exactly, even though the preimage recorded in the hunk was much larger (therefore the patch should not have applied), leading to a segfault. * "git rm foo && git commit foo" complained that 'foo' needs to be added first, instead of committing the removal, which was a nonsense. * git grep -c said "/dev/null: 0". * git-add -u failed to recognize a blob whose type changed between the index and the work tree. * The limit to rename detection has been tightened a lot to reduce performance problems with a huge change. * cvsimport and svnimport barfed when the input tried to move a tag. * "git apply -pN" did not chop the right number of directories. * "git svnimport" did not like SVN tags with funny characters in them. * git-gui 0.8.3, with assorted fixes, including: - font-chooser on X11 was unusable with large number of fonts; - a diff that contained a deleted symlink made it barf; - an untracked symbolic link to a directory made it fart; - a file with % in its name made it vomit; Documentation updates --------------------- User manual has been somewhat restructured. I think the new organization is much easier to read. cgit-1.1/git/Documentation/RelNotes/1.5.3.3.txt000066400000000000000000000016001300467324400207760ustar00rootroot00000000000000GIT v1.5.3.3 Release Notes ========================== Fixes since v1.5.3.2 -------------------- * git-quiltimport did not like it when a patch described in the series file does not exist. * p4 importer missed executable bit in some cases. * The default shell on some FreeBSD did not execute the argument parsing code correctly and made git unusable. * git-svn incorrectly spawned pager even when the user explicitly asked not to. * sample post-receive hook overquoted the envelope sender value. * git-am got confused when the patch contained a change that is only about type and not contents. * git-mergetool did not show our and their version of the conflicted file when started from a subdirectory of the project. * git-mergetool did not pass correct options when invoking diff3. * git-log sometimes invoked underlying "diff" machinery unnecessarily. cgit-1.1/git/Documentation/RelNotes/1.5.3.4.txt000066400000000000000000000022701300467324400210030ustar00rootroot00000000000000GIT v1.5.3.4 Release Notes ========================== Fixes since v1.5.3.3 -------------------- * Change to "git-ls-files" in v1.5.3.3 that was introduced to support partial commit of removal better had a segfaulting bug, which was diagnosed and fixed by Keith and Carl. * Performance improvements for rename detection has been backported from the 'master' branch. * "git-for-each-ref --format='%(numparent)'" was not working correctly at all, and --format='%(parent)' was not working for merge commits. * Sample "post-receive-hook" incorrectly sent out push notification e-mails marked as "From: " the committer of the commit that happened to be at the tip of the branch that was pushed, not from the person who pushed. * "git-remote" did not exit non-zero status upon error. * "git-add -i" did not respond very well to EOF from tty nor bogus input. * "git-rebase -i" squash subcommand incorrectly made the author of later commit the author of resulting commit, instead of taking from the first one in the squashed series. * "git-stash apply --index" was not documented. * autoconfiguration learned that "ar" command is found as "gas" on some systems. cgit-1.1/git/Documentation/RelNotes/1.5.3.5.txt000066400000000000000000000064601300467324400210110ustar00rootroot00000000000000GIT v1.5.3.5 Release Notes ========================== Fixes since v1.5.3.4 -------------------- * Comes with git-gui 0.8.4. * "git-config" silently ignored options after --list; now it will error out with a usage message. * "git-config --file" failed if the argument used a relative path as it changed directories before opening the file. * "git-config --file" now displays a proper error message if it cannot read the file specified on the command line. * "git-config", "git-diff", "git-apply" failed if run from a subdirectory with relative GIT_DIR and GIT_WORK_TREE set. * "git-blame" crashed if run during a merge conflict. * "git-add -i" did not handle single line hunks correctly. * "git-rebase -i" and "git-stash apply" failed if external diff drivers were used for one or more files in a commit. They now avoid calling the external diff drivers. * "git-log --follow" did not work unless diff generation (e.g. -p) was also requested. * "git-log --follow -B" did not work at all. Fixed. * "git-log -M -B" did not correctly handle cases of very large files being renamed and replaced by very small files in the same commit. * "git-log" printed extra newlines between commits when a diff was generated internally (e.g. -S or --follow) but not displayed. * "git-push" error message is more helpful when pushing to a repository with no matching refs and none specified. * "git-push" now respects + (force push) on wildcard refspecs, matching the behavior of git-fetch. * "git-filter-branch" now updates the working directory when it has finished filtering the current branch. * "git-instaweb" no longer fails on Mac OS X. * "git-cvsexportcommit" didn't always create new parent directories before trying to create new child directories. Fixed. * "git-fetch" printed a scary (but bogus) error message while fetching a tag that pointed to a tree or blob. The error did not impact correctness, only user perception. The bogus error is no longer printed. * "git-ls-files --ignored" did not properly descend into non-ignored directories that themselves contained ignored files if d_type was not supported by the filesystem. This bug impacted systems such as AFS. Fixed. * Git segfaulted when reading an invalid .gitattributes file. Fixed. * post-receive-email example hook was fixed for non-fast-forward updates. * Documentation updates for supported (but previously undocumented) options of "git-archive" and "git-reflog". * "make clean" no longer deletes the configure script that ships with the git tarball, making multiple architecture builds easier. * "git-remote show origin" spewed a warning message from Perl when no remote is defined for the current branch via branch..remote configuration settings. * Building with NO_PERL_MAKEMAKER excessively rebuilt contents of perl/ subdirectory by rewriting perl.mak. * http.sslVerify configuration settings were not used in scripted Porcelains. * "git-add" leaked a bit of memory while scanning for files to add. * A few workarounds to squelch false warnings from recent gcc have been added. * "git-send-pack $remote frotz" segfaulted when there is nothing named 'frotz' on the local end. * "git-rebase --interactive" did not handle its "--strategy" option properly. cgit-1.1/git/Documentation/RelNotes/1.5.3.6.txt000066400000000000000000000026301300467324400210050ustar00rootroot00000000000000GIT v1.5.3.6 Release Notes ========================== Fixes since v1.5.3.5 -------------------- * git-cvsexportcommit handles root commits better. * git-svn dcommit used to clobber when sending a series of patches. * git-svn dcommit failed after attempting to rebase when started with a dirty index; now it stops upfront. * git-grep sometimes refused to work when your index was unmerged. * "git-grep -A1 -B2" acted as if it was told to run "git -A1 -B21". * git-hash-object did not honor configuration variables, such as core.compression. * git-index-pack choked on a huge pack on 32-bit machines, even when large file offsets are supported. * atom feeds from git-web said "10" for the month of November. * a memory leak in commit walker was plugged. * When git-send-email inserted the original author's From: address in body, it did not mark the message with Content-type: as needed. * git-revert and git-cherry-pick incorrectly refused to start when the work tree was dirty. * git-clean did not honor core.excludesfile configuration. * git-add mishandled ".gitignore" files when applying them to subdirectories. * While importing a too branchy history, git-fastimport did not honor delta depth limit properly. * Support for zlib implementations that lack ZLIB_VERNUM and definition of deflateBound() has been added. * Quite a lot of documentation clarifications. cgit-1.1/git/Documentation/RelNotes/1.5.3.7.txt000066400000000000000000000030601300467324400210040ustar00rootroot00000000000000GIT v1.5.3.7 Release Notes ========================== Fixes since v1.5.3.6 -------------------- * git-send-email added 8-bit contents to the payload without marking it as 8-bit in a CTE header. * "git-bundle create a.bndl HEAD" dereferenced the symref and did not record the ref as 'HEAD'; this prevented a bundle from being used as a normal source of git-clone. * The code to reject nonsense command line of the form "git-commit -a paths..." and "git-commit --interactive paths..." were broken. * Adding a signature that is not ASCII-only to an original commit that is ASCII-only would make the result non-ASCII. "git-format-patch -s" did not mark such a message correctly with MIME encoding header. * git-add sometimes did not mark the resulting index entry stat-clean. This affected only cases when adding the contents with the same length as the previously staged contents, and the previous staging made the index entry "racily clean". * git-commit did not honor GIT_INDEX_FILE the user had in the environment. * When checking out a revision, git-checkout did not report where the updated HEAD is if you happened to have a file called HEAD in the work tree. * "git-rev-list --objects" mishandled a tree that points at a submodule. * "git cvsimport" was not ready for packed refs that "git gc" can produce and gave incorrect results. * Many scripted Porcelains were confused when you happened to have a file called "HEAD" in your work tree. Also it contains updates to the user manual and documentation. cgit-1.1/git/Documentation/RelNotes/1.5.3.8.txt000066400000000000000000000014171300467324400210110ustar00rootroot00000000000000GIT v1.5.3.8 Release Notes ========================== Fixes since v1.5.3.7 -------------------- * Some documentation used "email.com" as an example domain. * git-svn fix to handle funky branch and project names going over http/https correctly. * git-svn fix to tone down a needlessly alarming warning message. * git-clone did not correctly report errors while fetching over http. * git-send-email added redundant Message-Id: header to the outgoing e-mail when the patch text already had one. * a read-beyond-end-of-buffer bug in configuration file updater was fixed. * git-grep used to show the same hit repeatedly for unmerged paths. * After amending the patch title in "git-am -i", the command did not report the patch it applied with the updated title. cgit-1.1/git/Documentation/RelNotes/1.5.3.txt000066400000000000000000000333161300467324400206460ustar00rootroot00000000000000GIT v1.5.3 Release Notes ======================== Updates since v1.5.2 -------------------- * The commit walkers other than http are officially deprecated, but still supported for now. * The submodule support has Porcelain layer. Note that the current submodule support is minimal and this is deliberately so. A design decision we made is that operations at the supermodule level do not recurse into submodules by default. The expectation is that later we would add a mechanism to tell git which submodules the user is interested in, and this information might be used to determine the recursive behaviour of certain commands (e.g. "git checkout" and "git diff"), but currently we haven't agreed on what that mechanism should look like. Therefore, if you use submodules, you would probably need "git submodule update" on the submodules you care about after running a "git checkout" at the supermodule level. * There are a handful pack-objects changes to help you cope better with repositories with pathologically large blobs in them. * For people who need to import from Perforce, a front-end for fast-import is in contrib/fast-import/. * Comes with git-gui 0.8.2. * Comes with updated gitk. * New commands and options. - "git log --date=" can use more formats: iso8601, rfc2822. - The hunk header output from "git diff" family can be customized with the attributes mechanism. See gitattributes(5) for details. - "git stash" allows you to quickly save away your work in progress and replay it later on an updated state. - "git rebase" learned an "interactive" mode that let you pick and reorder which commits to rebuild. - "git fsck" can save its findings in $GIT_DIR/lost-found, without a separate invocation of "git lost-found" command. The blobs stored by lost-found are stored in plain format to allow you to grep in them. - $GIT_WORK_TREE environment variable can be used together with $GIT_DIR to work in a subdirectory of a working tree that is not located at "$GIT_DIR/..". - Giving "--file=" option to "git config" is the same as running the command with GIT_CONFIG= environment. - "git log" learned a new option "--follow", to follow renaming history of a single file. - "git filter-branch" lets you rewrite the revision history of specified branches. You can specify a number of filters to modify the commits, files and trees. - "git cvsserver" learned new options (--base-path, --export-all, --strict-paths) inspired by "git daemon". - "git daemon --base-path-relaxed" can help migrating a repository URL that did not use to use --base-path to use --base-path. - "git commit" can use "-t templatefile" option and commit.template configuration variable to prime the commit message given to you in the editor. - "git submodule" command helps you manage the projects from the superproject that contain them. - In addition to core.compression configuration option, core.loosecompression and pack.compression options can independently tweak zlib compression levels used for loose and packed objects. - "git ls-tree -l" shows size of blobs pointed at by the tree entries, similar to "/bin/ls -l". - "git rev-list" learned --regexp-ignore-case and --extended-regexp options to tweak its matching logic used for --grep filtering. - "git describe --contains" is a handier way to call more obscure command "git name-rev --tags". - "git gc --aggressive" tells the command to spend more cycles to optimize the repository harder. - "git repack" learned a "window-memory" limit which dynamically reduces the window size to stay within the specified memory usage. - "git repack" can be told to split resulting packs to avoid exceeding limit specified with "--max-pack-size". - "git fsck" gained --verbose option. This is really really verbose but it might help you identify exact commit that is corrupt in your repository. - "git format-patch" learned --numbered-files option. This may be useful for MH users. - "git format-patch" learned format.subjectprefix configuration variable, which serves the same purpose as "--subject-prefix" option. - "git tag -n -l" shows tag annotations while listing tags. - "git cvsimport" can optionally use the separate-remote layout. - "git blame" can be told to see through commits that change whitespaces and indentation levels with "-w" option. - "git send-email" can be told not to thread the messages when sending out more than one patches. - "git send-email" can also be told how to find whom to cc the message to for each message via --cc-cmd. - "git config" learned NUL terminated output format via -z to help scripts. - "git add" learned "--refresh ..." option to selectively refresh the cached stat information. - "git init -q" makes the command quieter. - "git -p command" now has a cousin of opposite sex, "git --no-pager command". * Updated behavior of existing commands. - "gitweb" can offer multiple snapshot formats. ***NOTE*** Unfortunately, this changes the format of the $feature{snapshot}{default} entry in the per-site configuration file 'gitweb_config.perl'. It used to be a three-element tuple that describe a single format; with the new configuration item format, you only have to say the name of the format ('tgz', 'tbz2' or 'zip'). Please update the your configuration file accordingly. - "git clone" uses -l (hardlink files under .git) by default when cloning locally. - URL used for "git clone" and friends can specify nonstandard SSH port by using ssh://host:port/path/to/repo syntax. - "git bundle create" can now create a bundle without negative refs, i.e. "everything since the beginning up to certain points". - "git diff" (but not the plumbing level "git diff-tree") now recursively descends into trees by default. - "git diff" does not show differences that come only from stat-dirtiness in the form of "diff --git" header anymore. It runs "update-index --refresh" silently as needed. - "git tag -l" used to match tags by globbing its parameter as if it has wildcard '*' on both ends, which made "git tag -l gui" to match tag 'gitgui-0.7.0'; this was very annoying. You now have to add asterisk on the sides you want to wildcard yourself. - The editor to use with many interactive commands can be overridden with GIT_EDITOR environment variable, or if it does not exist, with core.editor configuration variable. As before, if you have neither, environment variables VISUAL and EDITOR are consulted in this order, and then finally we fall back on "vi". - "git rm --cached" does not complain when removing a newly added file from the index anymore. - Options to "git log" to affect how --grep/--author options look for given strings now have shorter abbreviations. -i is for ignore case, and -E is for extended regexp. - "git log" learned --log-size to show the number of bytes in the log message part of the output to help qgit. - "git log --name-status" does not require you to give "-r" anymore. As a general rule, Porcelain commands should recurse when showing diff. - "git format-patch --root A" can be used to format everything since the beginning up to A. This was supported with "git format-patch --root A A" for a long time, but was not properly documented. - "git svn dcommit" retains local merge information. - "git svnimport" allows an empty string to be specified as the trunk/ directory. This is necessary to suck data from a SVN repository that doe not have trunk/ branches/ and tags/ organization at all. - "git config" to set values also honors type flags like --bool and --int. - core.quotepath configuration can be used to make textual git output to emit most of the characters in the path literally. - "git mergetool" chooses its backend more wisely, taking notice of its environment such as use of X, Gnome/KDE, etc. - "gitweb" shows merge commits a lot nicer than before. The default view uses more compact --cc format, while the UI allows to choose normal diff with any parent. - snapshot files "gitweb" creates from a repository at $path/$project/.git are more useful. We use $project part in the filename, which we used to discard. - "git cvsimport" creates lightweight tags; there is no interesting information we can record in an annotated tag, and the handcrafted ones the old code created was not properly formed anyway. - "git push" pretends that you immediately fetched back from the remote by updating corresponding remote tracking branches if you have any. - The diffstat given after a merge (or a pull) honors the color.diff configuration. - "git commit --amend" is now compatible with various message source options such as -m/-C/-c/-F. - "git apply --whitespace=strip" removes blank lines added at the end of the file. - "git fetch" over git native protocols with "-v" option shows connection status, and the IP address of the other end, to help diagnosing problems. - We used to have core.legacyheaders configuration, when set to false, allowed git to write loose objects in a format that mimics the format used by objects stored in packs. It turns out that this was not so useful. Although we will continue to read objects written in that format, we do not honor that configuration anymore and create loose objects in the legacy/traditional format. - "--find-copies-harder" option to diff family can now be spelled as "-C -C" for brevity. - "git mailsplit" (hence "git am") can read from Maildir formatted mailboxes. - "git cvsserver" does not barf upon seeing "cvs login" request. - "pack-objects" honors "delta" attribute set in .gitattributes. It does not attempt to deltify blobs that come from paths with delta attribute set to false. - "new-workdir" script (in contrib) can now be used with a bare repository. - "git mergetool" learned to use gvimdiff. - "gitview" (in contrib) has a better blame interface. - "git log" and friends did not handle a commit log message that is larger than 16kB; they do now. - "--pretty=oneline" output format for "git log" and friends deals with "malformed" commit log messages that have more than one lines in the first paragraph better. We used to show the first line, cutting the title at mid-sentence; we concatenate them into a single line and treat the result as "oneline". - "git p4import" has been demoted to contrib status. For a superior option, checkout the "git p4" front end to "git fast-import" (also in contrib). The man page and p4 rpm have been removed as well. - "git mailinfo" (hence "am") now tries to see if the message is in utf-8 first, instead of assuming iso-8859-1, if incoming e-mail does not say what encoding it is in. * Builds - old-style function definitions (most notably, a function without parameter defined with "func()", not "func(void)") have been eradicated. - "git tag" and "git verify-tag" have been rewritten in C. * Performance Tweaks - "git pack-objects" avoids re-deltification cost by caching small enough delta results it creates while looking for the best delta candidates. - "git pack-objects" learned a new heuristic to prefer delta that is shallower in depth over the smallest delta possible. This improves both overall packfile access performance and packfile density. - diff-delta code that is used for packing has been improved to work better on big files. - when there are more than one pack files in the repository, the runtime used to try finding an object always from the newest packfile; it now tries the same packfile as we found the object requested the last time, which exploits the locality of references. - verifying pack contents done by "git fsck --full" got boost by carefully choosing the order to verify objects in them. - "git read-tree -m" to read into an already populated index has been optimized vastly. The effect of this can be seen when switching branches that have differences in only a handful paths. - "git add paths..." and "git commit paths..." has also been heavily optimized. Fixes since v1.5.2 ------------------ All of the fixes in v1.5.2 maintenance series are included in this release, unless otherwise noted. * Bugfixes - "gitweb" had trouble handling non UTF-8 text with older Encode.pm Perl module. - "git svn" misparsed the data from the commits in the repository when the user had "color.diff = true" in the configuration. This has been fixed. - There was a case where "git svn dcommit" clobbered changes made on the SVN side while committing multiple changes. - "git-write-tree" had a bad interaction with racy-git avoidance and gitattributes mechanisms. - "git --bare command" overrode existing GIT_DIR setting and always made it treat the current working directory as GIT_DIR. - "git ls-files --error-unmatch" does not complain if you give the same path pattern twice by mistake. - "git init" autodetected core.filemode but not core.symlinks, which made a new directory created automatically by "git clone" cumbersome to use on filesystems that require these configurations to be set. - "git log" family of commands behaved differently when run as "git log" (no pathspec) and as "git log --" (again, no pathspec). This inconsistency was introduced somewhere in v1.3.0 series but now has been corrected. - "git rebase -m" incorrectly displayed commits that were skipped. cgit-1.1/git/Documentation/RelNotes/1.5.4.1.txt000066400000000000000000000010141300467324400207740ustar00rootroot00000000000000GIT v1.5.4.1 Release Notes ========================== Fixes since v1.5.4 ------------------ * "git-commit -C $tag" used to work but rewrite in C done in 1.5.4 broke it. * An entry in the .gitattributes file that names a pattern in a subdirectory of the directory it is in did not match correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should match "a/b/foo.c" but it didn't). * Customized color specification was parsed incorrectly when numeric color values are used. This was fixed in 1.5.4.1. cgit-1.1/git/Documentation/RelNotes/1.5.4.2.txt000066400000000000000000000027721300467324400210110ustar00rootroot00000000000000GIT v1.5.4.2 Release Notes ========================== Fixes since v1.5.4 ------------------ * The configuration parser was not prepared to see string valued variables misspelled as boolean and segfaulted. * Temporary files left behind due to interrupted object transfers were not cleaned up with "git prune". * "git config --unset" was confused when the unset variables were spelled with continuation lines in the config file. * The merge message detection in "git cvsimport" did not catch a message that began with "Merge...". * "git status" suggests "git rm --cached" for unstaging the earlier "git add" before the initial commit. * "git status" output was incorrect during a partial commit. * "git bisect" refused to start when the HEAD was detached. * "git bisect" allowed a wildcard character in the commit message expanded while writing its log file. * Manual pages were not formatted correctly with docbook xsl 1.72; added a workaround. * "git-commit -C $tag" used to work but rewrite in C done in 1.5.4 broke it. This was fixed in 1.5.4.1. * An entry in the .gitattributes file that names a pattern in a subdirectory of the directory it is in did not match correctly (e.g. pattern "b/*.c" in "a/.gitattributes" should match "a/b/foo.c" but it didn't). This was fixed in 1.5.4.1. * Customized color specification was parsed incorrectly when numeric color values are used. This was fixed in 1.5.4.1. * http transport misbehaved when linked with curl-gnutls. cgit-1.1/git/Documentation/RelNotes/1.5.4.3.txt000066400000000000000000000017451300467324400210110ustar00rootroot00000000000000GIT v1.5.4.3 Release Notes ========================== Fixes since v1.5.4.2 -------------------- * RPM spec used to pull in everything with 'git'. This has been changed so that 'git' package contains just the core parts, and we now supply 'git-all' metapackage to slurp in everything. This should match end user's expectation better. * When some refs failed to update, git-push reported "failure" which was unclear if some other refs were updated or all of them failed atomically (the answer is the former). Reworded the message to clarify this. * "git clone" from a repository whose HEAD was misconfigured did not set up the remote properly. Now it tries to do better. * Updated git-push documentation to clarify what "matching" means, in order to reduce user confusion. * Updated git-add documentation to clarify "add -u" operates in the current subdirectory you are in, just like other commands. * git-gui updates to work on OSX and Windows better. cgit-1.1/git/Documentation/RelNotes/1.5.4.4.txt000066400000000000000000000050711300467324400210060ustar00rootroot00000000000000GIT v1.5.4.4 Release Notes ========================== Fixes since v1.5.4.3 -------------------- * Building and installing with an overtight umask such as 077 made installed templates unreadable by others, while the rest of the install are done in a way that is friendly to umask 022. * "git cvsexportcommit -w $cvsdir" misbehaved when GIT_DIR is set to a relative directory. * "git http-push" had an invalid memory access that could lead it to segfault. * When "git rebase -i" gave control back to the user for a commit that is marked to be edited, it just said "modify it with commit --amend", without saying what to do to continue after modifying it. Give an explicit instruction to run "rebase --continue" to be more helpful. * "git send-email" in 1.5.4.3 issued a bogus empty In-Reply-To: header. * "git bisect" showed mysterious "won't bisect on seeked tree" error message. This was leftover from Cogito days to prevent "bisect" starting from a cg-seeked state. We still keep the Cogito safety, but running "git bisect start" when another bisect was in effect will clean up and start over. * "git push" with an explicit PATH to receive-pack did not quite work if receive-pack was not on usual PATH. We earlier fixed the same issue with "git fetch" and upload-pack, but somehow forgot to do so in the other direction. * git-gui's info dialog was not displayed correctly when the user tries to commit nothing (i.e. without staging anything). * "git revert" did not properly fail when attempting to run with a dirty index. * "git merge --no-commit --no-ff " incorrectly made commits. * "git merge --squash --no-ff ", which is a nonsense combination of options, was not rejected. * "git ls-remote" and "git remote show" against an empty repository failed, instead of just giving an empty result (regression). * "git fast-import" did not handle a renamed path whose name needs to be quoted, due to a bug in unquote_c_style() function. * "git cvsexportcommit" was confused when multiple files with the same basename needed to be pushed out in the same commit. * "git daemon" did not send early errors to syslog. * "git log --merge" did not work well with --left-right option. * "git svn" prompted for client cert password every time it accessed the server. * The reset command in "git fast-import" data stream was documented to end with an optional LF, but it actually required one. * "git svn dcommit/rebase" did not honor --rewrite-root option. Also included are a handful documentation updates. cgit-1.1/git/Documentation/RelNotes/1.5.4.5.txt000066400000000000000000000043011300467324400210020ustar00rootroot00000000000000GIT v1.5.4.5 Release Notes ========================== Fixes since v1.5.4.4 -------------------- * "git fetch there" when the URL information came from the Cogito style branches/there file did not update refs/heads/there (regression in 1.5.4). * Bogus refspec configuration such as "remote.there.fetch = =" were not detected as errors (regression in 1.5.4). * You couldn't specify a custom editor whose path contains a whitespace via GIT_EDITOR (and core.editor). * The subdirectory filter to "git filter-branch" mishandled a history where the subdirectory becomes empty and then later becomes non-empty. * "git shortlog" gave an empty line if the original commit message was malformed (e.g. a botched import from foreign SCM). Now it finds the first non-empty line and uses it for better information. * When the user fails to give a revision parameter to "git svn", an error from the Perl interpreter was issued because the script lacked proper error checking. * After "git rebase" stopped due to conflicts, if the user played with "git reset" and friends, "git rebase --abort" failed to go back to the correct commit. * Additional work trees prepared with git-new-workdir (in contrib/) did not share git-svn metadata directory .git/svn with the original. * "git-merge-recursive" did not mark addition of the same path with different filemodes correctly as a conflict. * "gitweb" gave malformed URL when pathinfo stype paths are in use. * "-n" stands for "--no-tags" again for "git fetch". * "git format-patch" did not detect the need to add 8-bit MIME header when the user used format.header configuration. * "rev~" revision specifier used to mean "rev", which was inconsistent with how "rev^" worked. Now "rev~" is the same as "rev~1" (hence it also is the same as "rev^1"), and "rev~0" is the same as "rev^0" (i.e. it has to be a commit). * "git quiltimport" did not grok empty lines, lines in "file -pNNN" format to specify the prefix levels and lines with trailing comments. * "git rebase -m" triggered pre-commit verification, which made "rebase --continue" impossible. As usual, it also comes with many documentation fixes and clarifications. cgit-1.1/git/Documentation/RelNotes/1.5.4.6.txt000066400000000000000000000026401300467324400210070ustar00rootroot00000000000000GIT v1.5.4.6 Release Notes ========================== I personally do not think there is any reason anybody should want to run v1.5.4.X series these days, because 'master' version is always more stable than any tagged released version of git. This is primarily to futureproof "git-shell" to accept requests without a dash between "git" and subcommand name (e.g. "git upload-pack") which the newer client will start to make sometime in the future. Fixes since v1.5.4.5 -------------------- * Command line option "-n" to "git-repack" was not correctly parsed. * Error messages from "git-apply" when the patchfile cannot be opened have been improved. * Error messages from "git-bisect" when given nonsense revisions have been improved. * reflog syntax that uses time e.g. "HEAD@{10 seconds ago}:path" did not stop parsing at the closing "}". * "git rev-parse --symbolic-full-name ^master^2" printed solitary "^", but it should print nothing. * "git apply" did not enforce "match at the beginning" correctly. * a path specification "a/b" in .gitattributes file should not match "sub/a/b", but it did. * "git log --date-order --topo-order" did not override the earlier date-order with topo-order as expected. * "git fast-export" did not export octopus merges correctly. * "git archive --prefix=$path/" mishandled gitattributes. As usual, it also comes with many documentation fixes and clarifications. cgit-1.1/git/Documentation/RelNotes/1.5.4.7.txt000066400000000000000000000005261300467324400210110ustar00rootroot00000000000000GIT v1.5.4.7 Release Notes ========================== Fixes since 1.5.4.7 ------------------- * Removed support for an obsolete gitweb request URI, whose implementation ran "git diff" Porcelain, instead of using plumbing, which would have run an external diff command specified in the repository configuration as the gitweb user. cgit-1.1/git/Documentation/RelNotes/1.5.4.txt000066400000000000000000000341301300467324400206420ustar00rootroot00000000000000GIT v1.5.4 Release Notes ======================== Removal ------- * "git svnimport" was removed in favor of "git svn". It is still there in the source tree (contrib/examples) but unsupported. * As git-commit and git-status have been rewritten, "git runstatus" helper script lost all its users and has been removed. Temporarily disabled -------------------- * "git http-push" is known not to work well with cURL library older than 7.16, and we had reports of repository corruption. It is disabled on such platforms for now. Unfortunately, 1.5.3.8 shares the same issue. In other words, this does not mean you will be fine if you stick to an older git release. For now, please do not use http-push from older git with cURL older than 7.16 if you value your data. A proper fix will hopefully materialize in later versions. Deprecation notices ------------------- * From v1.6.0, git will by default install dashed form of commands (e.g. "git-commit") outside of users' normal $PATH, and will install only selected commands ("git" itself, and "gitk") in $PATH. This implies: - Using dashed forms of git commands (e.g. "git-commit") from the command line has been informally deprecated since early 2006, but now it officially is, and will be removed in the future. Use dash-less forms (e.g. "git commit") instead. - Using dashed forms from your scripts, without first prepending the return value from "git --exec-path" to the scripts' PATH, has been informally deprecated since early 2006, but now it officially is. - Use of dashed forms with "PATH=$(git --exec-path):$PATH; export PATH" early in your script is not deprecated with this change. Users are strongly encouraged to adjust their habits and scripts now to prepare for this change. * The post-receive hook was introduced in March 2007 to supersede the post-update hook, primarily to overcome the command line length limitation of the latter. Use of post-update hook will be deprecated in future versions of git, starting from v1.6.0. * "git lost-found" was deprecated in favor of "git fsck"'s --lost-found option, and will be removed in the future. * "git peek-remote" is deprecated, as "git ls-remote" was written in C and works for all transports; "git peek-remote" will be removed in the future. * "git repo-config" which was an old name for "git config" command has been supported without being advertised for a long time. The next feature release will remove it. * From v1.6.0, the repack.usedeltabaseoffset config option will default to true, which will give denser packfiles (i.e. more efficient storage). The downside is that git older than version 1.4.4 will not be able to directly use a repository packed using this setting. * From v1.6.0, the pack.indexversion config option will default to 2, which is slightly more efficient, and makes repacking more immune to data corruptions. Git older than version 1.5.2 may revert to version 1 of the pack index with a manual "git index-pack" to be able to directly access corresponding pack files. Updates since v1.5.3 -------------------- * Comes with much improved gitk, with i18n. * Comes with git-gui 0.9.2 with i18n. * gitk is now merged as a subdirectory of git.git project, in preparation for its i18n. * progress displays from many commands are a lot nicer to the eye. Transfer commands show throughput data. * many commands that pay attention to per-directory .gitignore now do so lazily, which makes the usual case go much faster. * Output processing for '--pretty=format:' has been optimized. * Rename detection of diff family while detecting exact matches has been greatly optimized. * Rename detection of diff family tries to make more natural looking pairing. Earlier, if multiple identical rename sources were found in the preimage, the source used was picked pretty much at random. * Value "true" for color.diff and color.status configuration used to mean "always" (even when the output is not going to a terminal). This has been corrected to mean the same thing as "auto". * "git diff" Porcelain now respects diff.external configuration, which is another way to specify GIT_EXTERNAL_DIFF. * "git diff" can be told to use different prefixes other than "a/" and "b/" e.g. "git diff --src-prefix=l/ --dst-prefix=k/". * "git diff" sometimes did not quote paths with funny characters properly. * "git log" (and any revision traversal commands) misbehaved when --diff-filter is given but was not asked to actually produce diff. * HTTP proxy can be specified per remote repository using remote.*.httpproxy configuration, or global http.proxy configuration variable. * Various Perforce importer updates. * Example update and post-receive hooks have been improved. * Any command that wants to take a commit object name can now use ":/string" syntax to name a commit. * "git reset" is now built-in and its output can be squelched with -q. * "git reset --hard" does not make any sense in a bare repository, but did not error out; fixed. * "git send-email" can optionally talk over ssmtp and use SMTP-AUTH. * "git rebase" learned --whitespace option. * In "git rebase", when you decide not to replay a particular change after the command stopped with a conflict, you can say "git rebase --skip" without first running "git reset --hard", as the command now runs it for you. * "git rebase --interactive" mode can now work on detached HEAD. * Other minor to serious bugs in "git rebase -i" have been fixed. * "git rebase" now detaches head during its operation, so after a successful "git rebase" operation, the reflog entry branch@{1} for the current branch points at the commit before the rebase was started. * "git rebase -i" also triggers rerere to help your repeated merges. * "git merge" can call the "post-merge" hook. * "git pack-objects" can optionally run deltification with multiple threads. * "git archive" can optionally substitute keywords in files marked with export-subst attribute. * "git cherry-pick" made a misguided attempt to repeat the original command line in the generated log message, when told to cherry-pick a commit by naming a tag that points at it. It does not anymore. * "git for-each-ref" learned %(xxxdate:) syntax to show the various date fields in different formats. * "git gc --auto" is a low-impact way to automatically run a variant of "git repack" that does not lose unreferenced objects (read: safer than the usual one) after the user accumulates too many loose objects. * "git clean" has been rewritten in C. * You need to explicitly set clean.requireForce to "false" to allow "git clean" without -f to do any damage (lack of the configuration variable used to mean "do not require -f option to lose untracked files", but we now use the safer default). * The kinds of whitespace errors "git diff" and "git apply" notice (and fix) can be controlled via 'core.whitespace' configuration variable and 'whitespace' attribute in .gitattributes file. * "git push" learned --dry-run option to show what would happen if a push is run. * "git push" does not update a tracking ref on the local side when the remote refused to update the corresponding ref. * "git push" learned --mirror option. This is to push the local refs one-to-one to the remote, and deletes refs from the remote that do not exist anymore in the repository on the pushing side. * "git push" can remove a corrupt ref at the remote site with the usual ":ref" refspec. * "git remote" knows --mirror mode. This is to set up configuration to push into a remote repository to store local branch heads to the same branch on the remote side, and remove branch heads locally removed from local repository at the same time. Suitable for pushing into a back-up repository. * "git remote" learned "rm" subcommand. * "git cvsserver" can be run via "git shell". Also, "cvs" is recognized as a synonym for "git cvsserver", so that CVS users can be switched to git just by changing their login shell. * "git cvsserver" acts more like receive-pack by running post-receive and post-update hooks. * "git am" and "git rebase" are far less verbose. * "git pull" learned to pass --[no-]ff option to underlying "git merge". * "git pull --rebase" is a different way to integrate what you fetched into your current branch. * "git fast-export" produces data-stream that can be fed to fast-import to reproduce the history recorded in a git repository. * "git add -i" takes pathspecs to limit the set of files to work on. * "git add -p" is a short-hand to go directly to the selective patch subcommand in the interactive command loop and to exit when done. * "git add -i" UI has been colorized. The interactive prompt and menu can be colored by setting color.interactive configuration. The diff output (including the hunk picker) are colored with color.diff configuration. * "git commit --allow-empty" allows you to create a single-parent commit that records the same tree as its parent, overriding the usual safety valve. * "git commit --amend" can amend a merge that does not change the tree from its first parent. * "git commit" used to unconditionally strip comment lines that began with '#' and removed excess blank lines. This behavior has been made configurable. * "git commit" has been rewritten in C. * "git stash random-text" does not create a new stash anymore. It was a UI mistake. Use "git stash save random-text", or "git stash" (without extra args) for that. * "git stash clear extra-text" does not clear the whole stash anymore. It is tempting to expect "git stash clear stash@{2}" to drop only a single named stash entry, and it is rude to discard everything when that is asked (but not provided). * "git prune --expire