diff options
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Documentation/diff-options.txt | 3 | ||||
-rw-r--r-- | Documentation/git-grep.txt | 8 | ||||
-rw-r--r-- | Documentation/git.txt | 3 | ||||
-rw-r--r-- | Documentation/technical/racy-git.txt | 193 | ||||
-rw-r--r-- | INSTALL | 2 | ||||
-rw-r--r-- | Makefile | 135 | ||||
-rw-r--r-- | blame.c | 11 | ||||
-rw-r--r-- | builtin-apply.c | 265 | ||||
-rw-r--r-- | builtin-cat-file.c | 9 | ||||
-rw-r--r-- | builtin-checkout-index.c (renamed from checkout-index.c) | 27 | ||||
-rw-r--r-- | builtin-commit-tree.c | 2 | ||||
-rw-r--r-- | builtin-count-objects.c (renamed from builtin-count.c) | 0 | ||||
-rw-r--r-- | builtin-diff-files.c | 7 | ||||
-rw-r--r-- | builtin-diff-stages.c | 2 | ||||
-rw-r--r-- | builtin-diff.c | 12 | ||||
-rw-r--r-- | builtin-fmt-merge-msg.c | 2 | ||||
-rw-r--r-- | builtin-grep.c | 120 | ||||
-rw-r--r-- | builtin-ls-files.c | 27 | ||||
-rw-r--r-- | builtin-ls-tree.c | 6 | ||||
-rw-r--r-- | builtin-mailinfo.c | 6 | ||||
-rw-r--r-- | builtin-name-rev.c (renamed from name-rev.c) | 6 | ||||
-rw-r--r-- | builtin-pack-objects.c (renamed from pack-objects.c) | 56 | ||||
-rw-r--r-- | builtin-prune.c | 2 | ||||
-rw-r--r-- | builtin-push.c | 14 | ||||
-rw-r--r-- | builtin-read-tree.c | 863 | ||||
-rw-r--r-- | builtin-repo-config.c | 16 | ||||
-rw-r--r-- | builtin-rev-list.c | 6 | ||||
-rw-r--r-- | builtin-rev-parse.c | 10 | ||||
-rw-r--r-- | builtin-show-branch.c | 10 | ||||
-rw-r--r-- | builtin-symbolic-ref.c (renamed from symbolic-ref.c) | 4 | ||||
-rw-r--r-- | builtin-unpack-objects.c (renamed from unpack-objects.c) | 11 | ||||
-rw-r--r-- | builtin-update-index.c | 6 | ||||
-rw-r--r-- | builtin-verify-pack.c (renamed from verify-pack.c) | 15 | ||||
-rw-r--r-- | builtin.h | 80 | ||||
-rw-r--r-- | cache.h | 10 | ||||
-rw-r--r-- | check-racy.c | 28 | ||||
-rw-r--r-- | combine-diff.c | 31 | ||||
-rw-r--r-- | commit.c | 20 | ||||
-rw-r--r-- | config.mak.in | 16 | ||||
-rw-r--r-- | configure.ac | 240 | ||||
-rw-r--r-- | connect.c | 6 | ||||
-rw-r--r-- | contrib/emacs/vc-git.el | 2 | ||||
-rw-r--r-- | convert-objects.c | 2 | ||||
-rw-r--r-- | csum-file.c | 4 | ||||
-rw-r--r-- | daemon.c | 20 | ||||
-rw-r--r-- | describe.c | 6 | ||||
-rw-r--r-- | diff-lib.c | 2 | ||||
-rw-r--r-- | diff.c | 211 | ||||
-rw-r--r-- | diff.h | 3 | ||||
-rw-r--r-- | diffcore-break.c | 2 | ||||
-rw-r--r-- | diffcore-rename.c | 2 | ||||
-rw-r--r-- | dump-cache-tree.c | 2 | ||||
-rw-r--r-- | environment.c | 10 | ||||
-rw-r--r-- | exec_cmd.c | 2 | ||||
-rw-r--r-- | fetch-clone.c | 3 | ||||
-rw-r--r-- | fetch-pack.c | 6 | ||||
-rw-r--r-- | fsck-objects.c | 21 | ||||
-rw-r--r-- | git.c | 88 | ||||
-rw-r--r-- | gitweb/README | 30 | ||||
-rw-r--r-- | gitweb/git-logo.png | bin | 0 -> 208 bytes | |||
-rw-r--r-- | gitweb/gitweb.css | 42 | ||||
-rwxr-xr-x | gitweb/gitweb.perl (renamed from gitweb/gitweb.cgi) | 3078 | ||||
-rw-r--r-- | help.c (renamed from builtin-help.c) | 2 | ||||
-rw-r--r-- | http-fetch.c | 14 | ||||
-rw-r--r-- | http-push.c | 66 | ||||
-rw-r--r-- | index-pack.c | 12 | ||||
-rw-r--r-- | local-fetch.c | 8 | ||||
-rw-r--r-- | log-tree.c | 44 | ||||
-rw-r--r-- | merge-base.c | 2 | ||||
-rw-r--r-- | merge-index.c | 5 | ||||
-rw-r--r-- | merge-tree.c | 2 | ||||
-rw-r--r-- | mktree.c | 1 | ||||
-rw-r--r-- | object.c | 2 | ||||
-rw-r--r-- | pack-check.c | 6 | ||||
-rw-r--r-- | pack-redundant.c | 18 | ||||
-rw-r--r-- | pager.c | 4 | ||||
-rw-r--r-- | read-cache.c | 56 | ||||
-rw-r--r-- | receive-pack.c | 6 | ||||
-rw-r--r-- | refs.c | 8 | ||||
-rw-r--r-- | run-command.c | 8 | ||||
-rw-r--r-- | send-pack.c | 13 | ||||
-rw-r--r-- | sha1_file.c | 53 | ||||
-rw-r--r-- | sha1_name.c | 8 | ||||
-rw-r--r-- | ssh-fetch.c | 10 | ||||
-rw-r--r-- | ssh-upload.c | 4 | ||||
-rwxr-xr-x | t/t4116-apply-reverse.sh | 46 | ||||
-rwxr-xr-x | t/t7002-grep.sh | 31 | ||||
-rw-r--r-- | tree-diff.c | 15 | ||||
-rw-r--r-- | tree.c | 3 | ||||
-rw-r--r-- | unpack-trees.c | 799 | ||||
-rw-r--r-- | unpack-trees.h | 35 | ||||
-rw-r--r-- | upload-pack.c | 114 |
93 files changed, 4102 insertions, 3122 deletions
diff --git a/.gitignore b/.gitignore index fb0fa3f16a..55cd9844d6 100644 --- a/.gitignore +++ b/.gitignore @@ -125,6 +125,7 @@ git-verify-tag git-whatchanged git-write-tree git-core-*/?* +gitweb/gitweb.cgi test-date test-delta test-dump-cache-tree @@ -140,7 +141,7 @@ config.mak autom4te.cache config.log config.status -config.mak.in config.mak.autogen +config.mak.append configure git-blame diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt index 47ba9a403a..b5d9763594 100644 --- a/Documentation/diff-options.txt +++ b/Documentation/diff-options.txt @@ -36,6 +36,9 @@ Turn off colored diff, even when the configuration file gives the default to color output. +--color-words:: + Show colored word diff, i.e. color words which have changed. + --no-renames:: Turn off rename detection, even when the configuration file gives the default to do so. diff --git a/Documentation/git-grep.txt b/Documentation/git-grep.txt index dc7683383c..7545dd9a3e 100644 --- a/Documentation/git-grep.txt +++ b/Documentation/git-grep.txt @@ -11,7 +11,7 @@ SYNOPSIS [verse] 'git-grep' [--cached] [-a | --text] [-I] [-i | --ignore-case] [-w | --word-regexp] - [-v | --invert-match] + [-v | --invert-match] [--full-name] [-E | --extended-regexp] [-G | --basic-regexp] [-F | --fixed-strings] [-n] [-l | --files-with-matches] [-L | --files-without-match] [-c | --count] @@ -47,6 +47,12 @@ OPTIONS -v | --invert-match:: Select non-matching lines. +--full-name:: + When run from a subdirectory, the command usually + outputs paths relative to the current directory. This + option forces paths to be output relative to the project + top directory. + -E | --extended-regexp | -G | --basic-regexp:: Use POSIX extended/basic regexp for patterns. Default is to use basic regexp. diff --git a/Documentation/git.txt b/Documentation/git.txt index bcf187a11c..3de5fa9c82 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -633,6 +633,9 @@ git Diffs other ~~~~~ +'GIT_PAGER':: + This environment variable overrides `$PAGER`. + 'GIT_TRACE':: If this variable is set git will print `trace:` messages on stderr telling about alias expansion, built-in command diff --git a/Documentation/technical/racy-git.txt b/Documentation/technical/racy-git.txt new file mode 100644 index 0000000000..7597d04142 --- /dev/null +++ b/Documentation/technical/racy-git.txt @@ -0,0 +1,193 @@ +Use of index and Racy git problem +================================= + +Background +---------- + +The index is one of the most important data structure in git. +It represents a virtual working tree state by recording list of +paths and their object names and serves as a staging area to +write out the next tree object to be committed. The state is +"virtual" in the sense that it does not necessarily have to, and +often does not, match the files in the working tree. + +There are cases git needs to examine the differences between the +virtual working tree state in the index and the files in the +working tree. The most obvious case is when the user asks `git +diff` (or its low level implementation, `git diff-files`) or +`git-ls-files --modified`. In addition, git internally checks +if the files in the working tree is different from what are +recorded in the index to avoid stomping on local changes in them +during patch application, switching branches, and merging. + +In order to speed up this comparison between the files in the +working tree and the index entries, the index entries record the +information obtained from the filesystem via `lstat(2)` system +call when they were last updated. When checking if they differ, +git first runs `lstat(2)` on the files and compare the result +with this information (this is what was originally done by the +`ce_match_stat()` function, which the current code does in +`ce_match_stat_basic()` function). If some of these "cached +stat information" fields do not match, git can tell that the +files are modified without even looking at their contents. + +Note: not all members in `struct stat` obtained via `lstat(2)` +are used for this comparison. For example, `st_atime` obviously +is not useful. Currently, git compares the file type (regular +files vs symbolic links) and executable bits (only for regular +files) from `st_mode` member, `st_mtime` and `st_ctime` +timestamps, `st_uid`, `st_gid`, `st_ino`, and `st_size` members. +With a `USE_STDEV` compile-time option, `st_dev` is also +compared, but this is not enabled by default because this member +is not stable on network filesystems. With `USE_NSEC` +compile-time option, `st_mtim.tv_nsec` and `st_ctim.tv_nsec` +members are also compared, but this is not enabled by default +because the value of this member becomes meaningless once the +inode is evicted from the inode cache on filesystems that do not +store it on disk. + + +Racy git +-------- + +There is one slight problem with the optimization based on the +cached stat information. Consider this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + +The first `update-index` computes the object name of the +contents of file `foo` and updates the index entry for `foo` +along with the `struct stat` information. If the modification +that follows it happens very fast so that the file's `st_mtime` +timestamp does not change, after this sequence, the cached stat +information the index entry records still exactly match what you +can obtain from the filesystem, but the file `foo` is modified. +This way, git can incorrectly think files in the working tree +are unmodified even though they actually are. This is called +the "racy git" problem (discovered by Pasky), and the entries +that appear clean when they may not be because of this problem +are called "racily clean". + +To avoid this problem, git does two things: + +. When the cached stat information says the file has not been + modified, and the `st_mtime` is the same as (or newer than) + the timestamp of the index file itself (which is the time `git + update-index foo` finished running in the above example), it + also compares the contents with the object registered in the + index entry to make sure they match. + +. When the index file is updated that contains racily clean + entries, cached `st_size` information is truncated to zero + before writing a new version of the index file. + +Because the index file itself is written after collecting all +the stat information from updated paths, `st_mtime` timestamp of +it is usually the same as or newer than any of the paths the +index contains. And no matter how quick the modification that +follows `git update-index foo` finishes, the resulting +`st_mtime` timestamp on `foo` cannot get the timestamp earlier +than the index file. Therefore, index entries that can be +racily clean are limited to the ones that have the same +timestamp as the index file itself. + +The callers that want to check if an index entry matches the +corresponding file in the working tree continue to call +`ce_match_stat()`, but with this change, `ce_match_stat()` uses +`ce_modified_check_fs()` to see if racily clean ones are +actually clean after comparing the cached stat information using +`ce_match_stat_basic()`. + +The problem the latter solves is this sequence: + + $ git update-index 'foo' + : modify 'foo' in-place without changing its size + : wait for enough time + $ git update-index 'bar' + +Without the latter, the timestamp of the index file gets a newer +value, and falsely clean entry `foo` would not be caught by the +timestamp comparison check done with the former logic anymore. +The latter makes sure that the cached stat information for `foo` +would never match with the file in the working tree, so later +checks by `ce_match_stat_basic()` would report the index entry +does not match the file and git does not have to fall back on more +expensive `ce_modified_check_fs()`. + + +Runtime penalty +--------------- + +The runtime penalty of falling back to `ce_modified_check_fs()` +from `ce_match_stat()` can be very expensive when there are many +racily clean entries. An obvious way to artificially create +this situation is to give the same timestamp to all the files in +the working tree in a large project, run `git update-index` on +them, and give the same timestamp to the index file: + + $ date >.datestamp + $ git ls-files | xargs touch -r .datestamp + $ git ls-files | git update-index --stdin + $ touch -r .datestamp .git/index + +This will make all index entries racily clean. The linux-2.6 +project, for example, there are over 20,000 files in the working +tree. On my Athron 64X2 3800+, after the above: + + $ /usr/bin/time git diff-files + 1.68user 0.54system 0:02.22elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+67111minor)pagefaults 0swaps + $ git update-index MAINTAINERS + $ /usr/bin/time git diff-files + 0.02user 0.12system 0:00.14elapsed 100%CPU (0avgtext+0avgdata 0maxresident)k + 0inputs+0outputs (0major+935minor)pagefaults 0swaps + +Running `git update-index` in the middle checked the racily +clean entries, and left the cached `st_mtime` for all the paths +intact because they were actually clean (so this step took about +the same amount of time as the first `git diff-files`). After +that, they are not racily clean anymore but are truly clean, so +the second invocation of `git diff-files` fully took advantage +of the cached stat information. + + +Avoiding runtime penalty +------------------------ + +In order to avoid the above runtime penalty, the recent "master" +branch (post 1.4.2) has a code that makes sure the index file +gets timestamp newer than the youngest files in the index when +there are many young files with the same timestamp as the +resulting index file would otherwise would have by waiting +before finishing writing the index file out. + +I suspect that in practice the situation where many paths in the +index are all racily clean is quite rare. The only code paths +that can record recent timestamp for large number of paths I +know of are: + +. Initial `git add .` of a large project. + +. `git checkout` of a large project from an empty index into an + unpopulated working tree. + +Note: switching branches with `git checkout` keeps the cached +stat information of existing working tree files that are the +same between the current branch and the new branch, which are +all older than the resulting index file, and they will not +become racily clean. Only the files that are actually checked +out can become racily clean. + +In a large project where raciness avoidance cost really matters, +however, the initial computation of all object names in the +index takes more than one second, and the index file is written +out after all that happens. Therefore the timestamp of the +index file will be more than one seconds later than the the +youngest file in the working tree. This means that in these +cases there actually will not be any racily clean entry in +the resulting index. + +So in summary I think we should not worry about avoiding the +runtime penalty and get rid of the "wait before finishing +writing" code out. @@ -16,7 +16,7 @@ install" would not work. Alternatively you can use autoconf generated ./configure script to set up install paths (via config.mak.autogen), so you can write instead - $ autoconf ;# as yourself if ./configure doesn't exist yet + $ make configure ;# as yourself $ ./configure --prefix=/usr ;# as yourself $ make all doc ;# as yourself # make install install-doc ;# as root @@ -27,7 +27,7 @@ all: # 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. +# some C compilers supported these specifiers prior to C99 as an extension. # # Define NO_STRCASESTR if you don't have strcasestr. # @@ -121,6 +121,17 @@ template_dir = $(prefix)/share/git-core/templates/ GIT_PYTHON_DIR = $(prefix)/share/git-core/python # DESTDIR= +# default configuration for gitweb +GITWEB_CONFIG = gitweb_config.perl +GITWEB_HOME_LINK_STR = projects +GITWEB_SITENAME = +GITWEB_PROJECTROOT = /pub/git +GITWEB_BASE_URL = +GITWEB_LIST = +GITWEB_HOMETEXT = indextext.html +GITWEB_CSS = gitweb.css +GITWEB_LOGO = git-logo.png + export prefix bindir gitexecdir template_dir GIT_PYTHON_DIR CC = gcc @@ -173,32 +184,27 @@ SIMPLE_PROGRAMS = \ # ... and all the rest that could be moved out of bindir to gitexecdir PROGRAMS = \ - git-checkout-index$X \ git-convert-objects$X git-fetch-pack$X git-fsck-objects$X \ git-hash-object$X git-index-pack$X git-local-fetch$X \ git-merge-base$X \ - git-merge-index$X git-mktag$X git-mktree$X git-pack-objects$X git-patch-id$X \ + git-merge-index$X git-mktag$X git-mktree$X git-patch-id$X \ git-peek-remote$X git-receive-pack$X \ git-send-pack$X git-shell$X \ git-show-index$X git-ssh-fetch$X \ git-ssh-upload$X git-unpack-file$X \ - git-unpack-objects$X git-update-server-info$X \ + git-update-server-info$X \ git-upload-pack$X git-verify-pack$X \ - git-symbolic-ref$X \ - git-name-rev$X git-pack-redundant$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X git-imap-send$X - -BUILT_INS = git-log$X git-whatchanged$X git-show$X git-update-ref$X \ - git-count-objects$X git-diff$X git-push$X git-mailsplit$X \ - git-grep$X git-add$X git-rm$X git-rev-list$X git-stripspace$X \ - git-check-ref-format$X git-rev-parse$X git-mailinfo$X \ - git-init-db$X git-tar-tree$X git-upload-tar$X git-format-patch$X \ - git-ls-files$X git-ls-tree$X git-get-tar-commit-id$X \ - git-read-tree$X git-commit-tree$X git-write-tree$X \ - git-apply$X git-show-branch$X git-diff-files$X git-update-index$X \ - git-diff-index$X git-diff-stages$X git-diff-tree$X git-cat-file$X \ - git-fmt-merge-msg$X git-prune$X git-mv$X git-prune-packed$X \ - git-repo-config$X + git-pack-redundant$X git-var$X \ + git-describe$X git-merge-tree$X git-blame$X git-imap-send$X \ + $(EXTRA_PROGRAMS) + +# Empty... +EXTRA_PROGRAMS = + +BUILT_INS = \ + git-format-patch$X git-show$X git-whatchanged$X \ + git-get-tar-commit-id$X \ + $(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS)) # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -227,7 +233,7 @@ LIB_H = \ blob.h cache.h commit.h csum-file.h delta.h \ diff.h object.h pack.h pkt-line.h quote.h refs.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ - tree-walk.h log-tree.h dir.h path-list.h + tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -242,20 +248,50 @@ LIB_OBJS = \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ fetch-clone.o revision.o pager.o tree-walk.o xdiff-interface.o \ - alloc.o merge-file.o path-list.o $(DIFF_OBJS) + alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) BUILTIN_OBJS = \ - builtin-log.o builtin-help.o builtin-count.o builtin-diff.o builtin-push.o \ - builtin-grep.o builtin-add.o builtin-rev-list.o builtin-check-ref-format.o \ - builtin-rm.o builtin-init-db.o builtin-rev-parse.o \ - builtin-tar-tree.o builtin-upload-tar.o builtin-update-index.o \ - builtin-ls-files.o builtin-ls-tree.o builtin-write-tree.o \ - builtin-read-tree.o builtin-commit-tree.o builtin-mailinfo.o \ - builtin-apply.o builtin-show-branch.o builtin-diff-files.o \ - builtin-diff-index.o builtin-diff-stages.o builtin-diff-tree.o \ - builtin-cat-file.o builtin-mailsplit.o builtin-stripspace.o \ - builtin-update-ref.o builtin-fmt-merge-msg.o builtin-prune.o \ - builtin-mv.o builtin-prune-packed.o builtin-repo-config.o + builtin-add.o \ + builtin-apply.o \ + builtin-cat-file.o \ + builtin-checkout-index.o \ + builtin-check-ref-format.o \ + builtin-commit-tree.o \ + builtin-count-objects.o \ + builtin-diff.o \ + builtin-diff-files.o \ + builtin-diff-index.o \ + builtin-diff-stages.o \ + builtin-diff-tree.o \ + builtin-fmt-merge-msg.o \ + builtin-grep.o \ + builtin-init-db.o \ + builtin-log.o \ + builtin-ls-files.o \ + builtin-ls-tree.o \ + builtin-mailinfo.o \ + builtin-mailsplit.o \ + builtin-mv.o \ + builtin-name-rev.o \ + builtin-pack-objects.o \ + builtin-prune.o \ + builtin-prune-packed.o \ + builtin-push.o \ + builtin-read-tree.o \ + builtin-repo-config.o \ + builtin-rev-list.o \ + builtin-rev-parse.o \ + builtin-rm.o \ + builtin-show-branch.o \ + builtin-stripspace.o \ + builtin-symbolic-ref.o \ + builtin-tar-tree.o \ + builtin-unpack-objects.o \ + builtin-update-index.o \ + builtin-update-ref.o \ + builtin-upload-tar.o \ + builtin-verify-pack.o \ + builtin-write-tree.o GITLIBS = $(LIB_FILE) $(XDIFF_LIB) LIBS = $(GITLIBS) -lz @@ -296,7 +332,6 @@ ifeq ($(uname_S),SunOS) NEEDS_NSL = YesPlease SHELL_PATH = /bin/bash NO_STRCASESTR = YesPlease - NO_STRLCPY = YesPlease ifeq ($(uname_R),5.8) NEEDS_LIBICONV = YesPlease NO_UNSETENV = YesPlease @@ -525,7 +560,7 @@ LIB_OBJS += $(COMPAT_OBJS) export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir ### Build rules -all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk +all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi all: $(MAKE) -C templates @@ -538,7 +573,7 @@ git$X: git.c common-cmds.h $(BUILTIN_OBJS) $(GITLIBS) GIT-CFLAGS $(ALL_CFLAGS) -o $@ $(filter %.c,$^) \ $(BUILTIN_OBJS) $(ALL_LDFLAGS) $(LIBS) -builtin-help.o: common-cmds.h +help.o: common-cmds.h $(BUILT_INS): git$X rm -f $@ && ln git$X $@ @@ -583,6 +618,24 @@ git-status: git-commit cp $< $@+ mv $@+ $@ +gitweb/gitweb.cgi: gitweb/gitweb.perl + rm -f $@ $@+ + sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \ + -e 's|++GIT_VERSION++|$(GIT_VERSION)|g' \ + -e 's|++GIT_BINDIR++|$(bindir)|g' \ + -e 's|++GITWEB_CONFIG++|$(GITWEB_CONFIG)|g' \ + -e 's|++GITWEB_HOME_LINK_STR++|$(GITWEB_HOME_LINK_STR)|g' \ + -e 's|++GITWEB_SITENAME++|$(GITWEB_SITENAME)|g' \ + -e 's|++GITWEB_PROJECTROOT++|$(GITWEB_PROJECTROOT)|g' \ + -e 's|++GITWEB_BASE_URL++|$(GITWEB_BASE_URL)|g' \ + -e 's|++GITWEB_LIST++|$(GITWEB_LIST)|g' \ + -e 's|++GITWEB_HOMETEXT++|$(GITWEB_HOMETEXT)|g' \ + -e 's|++GITWEB_CSS++|$(GITWEB_CSS)|g' \ + -e 's|++GITWEB_LOGO++|$(GITWEB_LOGO)|g' \ + $< >$@+ + chmod +x $@+ + mv $@+ $@ + git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css rm -f $@ $@+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ @@ -593,10 +646,17 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css -e '/@@GITWEB_CGI@@/d' \ -e '/@@GITWEB_CSS@@/r gitweb/gitweb.css' \ -e '/@@GITWEB_CSS@@/d' \ - $@.sh | sed "s|/usr/bin/git|$(bindir)/git|" > $@+ + $@.sh > $@+ chmod +x $@+ mv $@+ $@ +configure: configure.ac + rm -f $@ $<+ + sed -e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \ + $< > $<+ + autoconf -o $@ $<+ + rm -f $<+ + # These can record GIT_VERSION git$X git.spec \ $(patsubst %.sh,%,$(SCRIPT_SH)) \ @@ -788,10 +848,11 @@ clean: rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags rm -rf autom4te.cache - rm -f config.log config.mak.autogen configure config.status config.cache + rm -f configure config.log config.mak.autogen config.mak.append config.status config.cache rm -rf $(GIT_TARNAME) .doc-tmp-dir rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz rm -f $(htmldocs).tar.gz $(manpages).tar.gz + rm -f gitweb/gitweb.cgi $(MAKE) -C Documentation/ clean $(MAKE) -C templates clean $(MAKE) -C t/ clean @@ -56,9 +56,9 @@ struct patch { static void get_blob(struct commit *commit); /* Only used for statistics */ -static int num_get_patch = 0; -static int num_commits = 0; -static int patch_time = 0; +static int num_get_patch; +static int num_commits; +static int patch_time; struct blame_diff_state { struct xdiff_emit_state xm; @@ -351,10 +351,7 @@ static int fill_util_info(struct commit *commit) assert(util); assert(util->pathname); - if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) - return 1; - else - return 0; + return !!get_blob_sha1(commit->tree, util->pathname, util->sha1); } static void alloc_line_map(struct commit *commit) diff --git a/builtin-apply.c b/builtin-apply.c index 9cf477c701..4f0eef0ac3 100644 --- a/builtin-apply.c +++ b/builtin-apply.c @@ -28,17 +28,18 @@ static int prefix_length = -1; static int newfd = -1; static int p_value = 1; -static int allow_binary_replacement = 0; -static int check_index = 0; -static int write_index = 0; -static int cached = 0; -static int diffstat = 0; -static int numstat = 0; -static int summary = 0; -static int check = 0; +static int allow_binary_replacement; +static int check_index; +static int write_index; +static int cached; +static int diffstat; +static int numstat; +static int summary; +static int check; static int apply = 1; -static int no_add = 0; -static int show_index_info = 0; +static int apply_in_reverse; +static int no_add; +static int show_index_info; static int line_termination = '\n'; static unsigned long p_context = -1; static const char apply_usage[] = @@ -50,10 +51,10 @@ static enum whitespace_eol { error_on_whitespace, strip_whitespace, } new_whitespace = warn_on_whitespace; -static int whitespace_error = 0; +static int whitespace_error; static int squelch_whitespace_errors = 5; -static int applied_after_stripping = 0; -static const char *patch_input_file = NULL; +static int applied_after_stripping; +static const char *patch_input_file; static void parse_whitespace_option(const char *option) { @@ -108,6 +109,13 @@ static int max_change, max_len; */ static int linenr = 1; +/* + * This represents one "hunk" from a patch, starting with + * "@@ -oldpos,oldlines +newpos,newlines @@" marker. The + * patch text is pointed at by patch, and its byte length + * is stored in size. leading and trailing are the number + * of context lines. + */ struct fragment { unsigned long leading, trailing; unsigned long oldpos, oldlines; @@ -117,12 +125,19 @@ struct fragment { struct fragment *next; }; +/* + * When dealing with a binary patch, we reuse "leading" field + * to store the type of the binary hunk, either deflated "delta" + * or deflated "literal". + */ +#define binary_patch_method leading +#define BINARY_DELTA_DEFLATED 1 +#define BINARY_LITERAL_DEFLATED 2 + struct patch { char *new_name, *old_name, *def_name; unsigned int old_mode, new_mode; - int is_rename, is_copy, is_new, is_delete, is_binary, is_reverse; -#define BINARY_DELTA_DEFLATED 1 -#define BINARY_LITERAL_DEFLATED 2 + int is_rename, is_copy, is_new, is_delete, is_binary; unsigned long deflate_origlen; int lines_added, lines_deleted; int score; @@ -978,43 +993,70 @@ static inline int metadata_changes(struct patch *patch) patch->old_mode != patch->new_mode); } -static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +static char *inflate_it(const void *data, unsigned long size, + unsigned long inflated_size) { - /* We have read "GIT binary patch\n"; what follows is a line - * that says the patch method (currently, either "deflated - * literal" or "deflated delta") and the length of data before - * deflating; a sequence of 'length-byte' followed by base-85 - * encoded data follows. + z_stream stream; + void *out; + int st; + + memset(&stream, 0, sizeof(stream)); + + stream.next_in = (unsigned char *)data; + stream.avail_in = size; + stream.next_out = out = xmalloc(inflated_size); + stream.avail_out = inflated_size; + inflateInit(&stream); + st = inflate(&stream, Z_FINISH); + if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { + free(out); + return NULL; + } + return out; +} + +static struct fragment *parse_binary_hunk(char **buf_p, + unsigned long *sz_p, + int *status_p, + int *used_p) +{ + /* Expect a line that begins with binary patch method ("literal" + * or "delta"), followed by the length of data before deflating. + * a sequence of 'length-byte' followed by base-85 encoded data + * should follow, terminated by a newline. * * Each 5-byte sequence of base-85 encodes up to 4 bytes, * and we would limit the patch line to 66 characters, * so one line can fit up to 13 groups that would decode * to 52 bytes max. The length byte 'A'-'Z' corresponds * to 1-26 bytes, and 'a'-'z' corresponds to 27-52 bytes. - * The end of binary is signaled with an empty line. */ int llen, used; - struct fragment *fragment; + unsigned long size = *sz_p; + char *buffer = *buf_p; + int patch_method; + unsigned long origlen; char *data = NULL; + int hunk_size = 0; + struct fragment *frag; - patch->fragments = fragment = xcalloc(1, sizeof(*fragment)); - - /* Grab the type of patch */ llen = linelen(buffer, size); used = llen; - linenr++; + + *status_p = 0; if (!strncmp(buffer, "delta ", 6)) { - patch->is_binary = BINARY_DELTA_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 6, NULL, 10); + patch_method = BINARY_DELTA_DEFLATED; + origlen = strtoul(buffer + 6, NULL, 10); } else if (!strncmp(buffer, "literal ", 8)) { - patch->is_binary = BINARY_LITERAL_DEFLATED; - patch->deflate_origlen = strtoul(buffer + 8, NULL, 10); + patch_method = BINARY_LITERAL_DEFLATED; + origlen = strtoul(buffer + 8, NULL, 10); } else - return error("unrecognized binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + return NULL; + + linenr++; buffer += llen; while (1) { int byte_length, max_byte_length, newsize; @@ -1043,21 +1085,79 @@ static int parse_binary(char *buffer, unsigned long size, struct patch *patch) if (max_byte_length < byte_length || byte_length <= max_byte_length - 4) goto corrupt; - newsize = fragment->size + byte_length; + newsize = hunk_size + byte_length; data = xrealloc(data, newsize); - if (decode_85(data + fragment->size, - buffer + 1, - byte_length)) + if (decode_85(data + hunk_size, buffer + 1, byte_length)) goto corrupt; - fragment->size = newsize; + hunk_size = newsize; buffer += llen; size -= llen; } - fragment->patch = data; - return used; + + frag = xcalloc(1, sizeof(*frag)); + frag->patch = inflate_it(data, hunk_size, origlen); + if (!frag->patch) + goto corrupt; + free(data); + frag->size = origlen; + *buf_p = buffer; + *sz_p = size; + *used_p = used; + frag->binary_patch_method = patch_method; + return frag; + corrupt: - return error("corrupt binary patch at line %d: %.*s", - linenr-1, llen-1, buffer); + if (data) + free(data); + *status_p = -1; + error("corrupt binary patch at line %d: %.*s", + linenr-1, llen-1, buffer); + return NULL; +} + +static int parse_binary(char *buffer, unsigned long size, struct patch *patch) +{ + /* We have read "GIT binary patch\n"; what follows is a line + * that says the patch method (currently, either "literal" or + * "delta") and the length of data before deflating; a + * sequence of 'length-byte' followed by base-85 encoded data + * follows. + * + * When a binary patch is reversible, there is another binary + * hunk in the same format, starting with patch method (either + * "literal" or "delta") with the length of data, and a sequence + * of length-byte + base-85 encoded data, terminated with another + * empty line. This data, when applied to the postimage, produces + * the preimage. + */ + struct fragment *forward; + struct fragment *reverse; + int status; + int used, used_1; + + forward = parse_binary_hunk(&buffer, &size, &status, &used); + if (!forward && !status) + /* there has to be one hunk (forward hunk) */ + return error("unrecognized binary patch at line %d", linenr-1); + if (status) + /* otherwise we already gave an error message */ + return status; + + reverse = parse_binary_hunk(&buffer, &size, &status, &used_1); + if (reverse) + used += used_1; + else if (status) { + /* not having reverse hunk is not an error, but having + * a corrupt reverse hunk is. + */ + free((void*) forward->patch); + free(forward); + return status; + } + forward->next = reverse; + patch->fragments = forward; + patch->is_binary = 1; + return used; } static int parse_chunk(char *buffer, unsigned long size, struct patch *patch) @@ -1143,7 +1243,6 @@ static void reverse_patches(struct patch *p) swap(frag->newpos, frag->oldpos); swap(frag->newlines, frag->oldlines); } - p->is_reverse = !p->is_reverse; } } @@ -1363,8 +1462,7 @@ static int apply_line(char *output, const char *patch, int plen) return plen; } -static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, - int reverse, int inaccurate_eof) +static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, int inaccurate_eof) { int match_beginning, match_end; char *buf = desc->buffer; @@ -1396,7 +1494,7 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, if (len < size && patch[len] == '\\') plen--; first = *patch; - if (reverse) { + if (apply_in_reverse) { if (first == '-') first = '+'; else if (first == '+') @@ -1506,28 +1604,6 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag, return offset; } -static char *inflate_it(const void *data, unsigned long size, - unsigned long inflated_size) -{ - z_stream stream; - void *out; - int st; - - memset(&stream, 0, sizeof(stream)); - - stream.next_in = (unsigned char *)data; - stream.avail_in = size; - stream.next_out = out = xmalloc(inflated_size); - stream.avail_out = inflated_size; - inflateInit(&stream); - st = inflate(&stream, Z_FINISH); - if ((st != Z_STREAM_END) || stream.total_out != inflated_size) { - free(out); - return NULL; - } - return out; -} - static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) { unsigned long dst_size; @@ -1535,30 +1611,29 @@ static int apply_binary_fragment(struct buffer_desc *desc, struct patch *patch) void *data; void *result; - /* Binary patch is irreversible */ - if (patch->is_reverse) - return error("cannot reverse-apply a binary patch to '%s'", - patch->new_name - ? patch->new_name : patch->old_name); - - data = inflate_it(fragment->patch, fragment->size, - patch->deflate_origlen); - if (!data) - return error("corrupt patch data"); - switch (patch->is_binary) { + /* Binary patch is irreversible without the optional second hunk */ + if (apply_in_reverse) { + if (!fragment->next) + return error("cannot reverse-apply a binary patch " + "without the reverse hunk to '%s'", + patch->new_name + ? patch->new_name : patch->old_name); + fragment = fragment; + } + data = (void*) fragment->patch; + switch (fragment->binary_patch_method) { case BINARY_DELTA_DEFLATED: result = patch_delta(desc->buffer, desc->size, data, - patch->deflate_origlen, + fragment->size, &dst_size); free(desc->buffer); desc->buffer = result; - free(data); break; case BINARY_LITERAL_DEFLATED: free(desc->buffer); desc->buffer = data; - dst_size = patch->deflate_origlen; + dst_size = fragment->size; break; } if (!desc->buffer) @@ -1609,7 +1684,7 @@ static int apply_binary(struct buffer_desc *desc, struct patch *patch) } get_sha1_hex(patch->new_sha1_prefix, sha1); - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { free(desc->buffer); desc->alloc = desc->size = 0; desc->buffer = NULL; @@ -1657,8 +1732,7 @@ static int apply_fragments(struct buffer_desc *desc, struct patch *patch) return apply_binary(desc, patch); while (frag) { - if (apply_one_fragment(desc, frag, patch->is_reverse, - patch->inaccurate_eof) < 0) + if (apply_one_fragment(desc, frag, patch->inaccurate_eof) < 0) return error("patch failed: %s:%ld", name, frag->oldpos); frag = frag->next; @@ -1842,11 +1916,6 @@ static int check_patch_list(struct patch *patch) return error; } -static inline int is_null_sha1(const unsigned char *sha1) -{ - return !memcmp(sha1, null_sha1, 20); -} - static void show_index_list(struct patch *list) { struct patch *patch; @@ -2194,8 +2263,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd, const char *filename, - int reverse, int inaccurate_eof) +static int apply_patch(int fd, const char *filename, int inaccurate_eof) { unsigned long offset, size; char *buffer = read_patch_file(fd, &size); @@ -2215,7 +2283,7 @@ static int apply_patch(int fd, const char *filename, nr = parse_chunk(buffer + offset, size, patch); if (nr < 0) break; - if (reverse) + if (apply_in_reverse) reverse_patches(patch); if (use_patch(patch)) { patch_stats(patch); @@ -2278,7 +2346,6 @@ int cmd_apply(int argc, const char **argv, const char *prefix) { int i; int read_stdin = 1; - int reverse = 0; int inaccurate_eof = 0; const char *whitespace_option = NULL; @@ -2289,7 +2356,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) int fd; if (!strcmp(arg, "-")) { - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + apply_patch(0, "<stdin>", inaccurate_eof); read_stdin = 0; continue; } @@ -2367,7 +2434,7 @@ int cmd_apply(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "-R") || !strcmp(arg, "--reverse")) { - reverse = 1; + apply_in_reverse = 1; continue; } if (!strcmp(arg, "--inaccurate-eof")) { @@ -2390,12 +2457,12 @@ int cmd_apply(int argc, const char **argv, const char *prefix) usage(apply_usage); read_stdin = 0; set_default_whitespace_mode(whitespace_option); - apply_patch(fd, arg, reverse, inaccurate_eof); + apply_patch(fd, arg, inaccurate_eof); close(fd); } set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0, "<stdin>", reverse, inaccurate_eof); + apply_patch(0, "<stdin>", inaccurate_eof); if (whitespace_error) { if (squelch_whitespace_errors && squelch_whitespace_errors < whitespace_error) { diff --git a/builtin-cat-file.c b/builtin-cat-file.c index 814fb0743f..df009ade7a 100644 --- a/builtin-cat-file.c +++ b/builtin-cat-file.c @@ -26,7 +26,7 @@ static void flush_buffer(const char *buf, unsigned long size) } } -static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +static void pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) { /* the parser in tag.c is useless here. */ const char *endp = buf + size; @@ -91,7 +91,6 @@ static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long */ if (cp < endp) flush_buffer(cp, endp - cp); - return 0; } int cmd_cat_file(int argc, const char **argv, const char *prefix) @@ -145,8 +144,10 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) buf = read_sha1_file(sha1, type, &size); if (!buf) die("Cannot read object %s", argv[2]); - if (!strcmp(type, tag_type)) - return pprint_tag(sha1, buf, size); + if (!strcmp(type, tag_type)) { + pprint_tag(sha1, buf, size); + return 0; + } /* otherwise just spit out the data */ break; diff --git a/checkout-index.c b/builtin-checkout-index.c index dfb1c44415..6b55f931cb 100644 --- a/checkout-index.c +++ b/builtin-checkout-index.c @@ -42,8 +42,6 @@ #include "cache-tree.h" #define CHECKOUT_ALL 4 -static const char *prefix; -static int prefix_length; static int line_termination = '\n'; static int checkout_stage; /* default to checkout stage0 */ static int to_tempfile; @@ -51,7 +49,7 @@ static char topath[4][MAXPATHLEN+1]; static struct checkout state; -static void write_tempfile_record (const char *name) +static void write_tempfile_record(const char *name, int prefix_length) { int i; @@ -77,7 +75,7 @@ static void write_tempfile_record (const char *name) } } -static int checkout_file(const char *name) +static int checkout_file(const char *name, int prefix_length) { int namelen = strlen(name); int pos = cache_name_pos(name, namelen); @@ -106,7 +104,7 @@ static int checkout_file(const char *name) if (did_checkout) { if (to_tempfile) - write_tempfile_record(name); + write_tempfile_record(name, prefix_length); return errs > 0 ? -1 : 0; } @@ -124,7 +122,7 @@ static int checkout_file(const char *name) return -1; } -static int checkout_all(void) +static void checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry* last_ce = NULL; @@ -141,7 +139,7 @@ static int checkout_all(void) if (last_ce && to_tempfile) { if (ce_namelen(last_ce) != ce_namelen(ce) || memcmp(last_ce->name, ce->name, ce_namelen(ce))) - write_tempfile_record(last_ce->name); + write_tempfile_record(last_ce->name, prefix_length); } if (checkout_entry(ce, &state, to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) @@ -149,13 +147,12 @@ static int checkout_all(void) last_ce = ce; } if (last_ce && to_tempfile) - write_tempfile_record(last_ce->name); + write_tempfile_record(last_ce->name, prefix_length); if (errs) /* we have already done our error reporting. * exit with the same code as die(). */ exit(128); - return 0; } static const char checkout_cache_usage[] = @@ -163,16 +160,16 @@ static const char checkout_cache_usage[] = static struct lock_file lock_file; -int main(int argc, char **argv) +int cmd_checkout_index(int argc, const char **argv, const char *prefix) { int i; int newfd = -1; int all = 0; int read_from_stdin = 0; + int prefix_length; - state.base_dir = ""; - prefix = setup_git_directory(); git_config(git_default_config); + state.base_dir = ""; prefix_length = prefix ? strlen(prefix) : 0; if (read_cache() < 0) { @@ -270,7 +267,7 @@ int main(int argc, char **argv) if (read_from_stdin) die("git-checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - checkout_file(p); + checkout_file(p, prefix_length); if (p < arg || p > arg + strlen(arg)) free((char*)p); } @@ -292,7 +289,7 @@ int main(int argc, char **argv) else path_name = buf.buf; p = prefix_path(prefix, prefix_length, path_name); - checkout_file(p); + checkout_file(p, prefix_length); if (p < path_name || p > path_name + strlen(path_name)) free((char *)p); if (path_name != buf.buf) @@ -301,7 +298,7 @@ int main(int argc, char **argv) } if (all) - checkout_all(); + checkout_all(prefix, prefix_length); if (0 <= newfd && (write_cache(newfd, active_cache, active_nr) || diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index 9c98796671..e2e690a1ae 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -69,7 +69,7 @@ static int new_parent(int idx) int i; unsigned char *sha1 = parent_sha1[idx]; for (i = 0; i < idx; i++) { - if (!memcmp(parent_sha1[i], sha1, 20)) { + if (!hashcmp(parent_sha1[i], sha1)) { error("duplicate parent %s ignored", sha1_to_hex(sha1)); return 0; } diff --git a/builtin-count.c b/builtin-count-objects.c index 1d3729aa99..1d3729aa99 100644 --- a/builtin-count.c +++ b/builtin-count-objects.c diff --git a/builtin-diff-files.c b/builtin-diff-files.c index ac13db70ff..5d4a5c5828 100644 --- a/builtin-diff-files.c +++ b/builtin-diff-files.c @@ -47,12 +47,5 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) if (rev.pending.nr || rev.min_age != -1 || rev.max_age != -1) usage(diff_files_usage); - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (rev.diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - rev.diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(&rev, silent); } diff --git a/builtin-diff-stages.c b/builtin-diff-stages.c index 5960e08997..70bb89808d 100644 --- a/builtin-diff-stages.c +++ b/builtin-diff-stages.c @@ -46,7 +46,7 @@ static void diff_stages(int stage1, int stage2, const char **pathspec) else if (!two) diff_addremove(&diff_options, '-', ntohl(one->ce_mode), one->sha1, name, NULL); - else if (memcmp(one->sha1, two->sha1, 20) || + else if (hashcmp(one->sha1, two->sha1) || (one->ce_mode != two->ce_mode) || diff_options.find_copies_harder) diff_change(&diff_options, diff --git a/builtin-diff.c b/builtin-diff.c index a090e298a5..874f773421 100644 --- a/builtin-diff.c +++ b/builtin-diff.c @@ -56,13 +56,6 @@ static int builtin_diff_files(struct rev_info *revs, if (revs->max_count < 0 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) revs->combine_merges = revs->dense_combined_merges = 1; - /* - * Backward compatibility wart - "diff-files -s" used to - * defeat the common diff option "-s" which asked for - * DIFF_FORMAT_NO_OUTPUT. - */ - if (revs->diffopt.output_format == DIFF_FORMAT_NO_OUTPUT) - revs->diffopt.output_format = DIFF_FORMAT_RAW; return run_diff_files(revs, silent); } @@ -75,9 +68,8 @@ static void stuff_change(struct diff_options *opt, { struct diff_filespec *one, *two; - if (memcmp(null_sha1, old_sha1, 20) && - memcmp(null_sha1, new_sha1, 20) && - !memcmp(old_sha1, new_sha1, 20)) + if (!is_null_sha1(old_sha1) && !is_null_sha1(new_sha1) && + !hashcmp(old_sha1, new_sha1)) return; if (opt->reverse_diff) { diff --git a/builtin-fmt-merge-msg.c b/builtin-fmt-merge-msg.c index 485ede7cad..28b5dfd054 100644 --- a/builtin-fmt-merge-msg.c +++ b/builtin-fmt-merge-msg.c @@ -8,7 +8,7 @@ static const char *fmt_merge_msg_usage = "git-fmt-merge-msg [--summary] [--no-summary] [--file <file>]"; -static int merge_summary = 0; +static int merge_summary; static int fmt_merge_msg_config(const char *key, const char *value) { diff --git a/builtin-grep.c b/builtin-grep.c index 93b7e07b30..0bd517b264 100644 --- a/builtin-grep.c +++ b/builtin-grep.c @@ -123,6 +123,7 @@ struct grep_opt { struct grep_pat *pattern_list; struct grep_pat **pattern_tail; struct grep_expr *pattern_expression; + int prefix_length; regex_t regexp; unsigned linenum:1; unsigned invert:1; @@ -136,6 +137,7 @@ struct grep_opt { #define GREP_BINARY_TEXT 2 unsigned binary:2; unsigned extended:1; + unsigned relative:1; int regflags; unsigned pre_context; unsigned post_context; @@ -173,61 +175,12 @@ static void compile_regexp(struct grep_pat *p, struct grep_opt *opt) } } -#if DEBUG -static inline void indent(int in) -{ - int i; - for (i = 0; i < in; i++) putchar(' '); -} - -static void dump_pattern_exp(struct grep_expr *x, int in) -{ - switch (x->node) { - case GREP_NODE_ATOM: - indent(in); - puts(x->u.atom->pattern); - break; - case GREP_NODE_NOT: - indent(in); - puts("--not"); - dump_pattern_exp(x->u.unary, in+1); - break; - case GREP_NODE_AND: - dump_pattern_exp(x->u.binary.left, in+1); - indent(in); - puts("--and"); - dump_pattern_exp(x->u.binary.right, in+1); - break; - case GREP_NODE_OR: - dump_pattern_exp(x->u.binary.left, in+1); - indent(in); - puts("--or"); - dump_pattern_exp(x->u.binary.right, in+1); - break; - } -} - -static void looking_at(const char *msg, struct grep_pat **list) -{ - struct grep_pat *p = *list; - fprintf(stderr, "%s: looking at ", msg); - if (!p) - fprintf(stderr, "empty\n"); - else - fprintf(stderr, "<%s>\n", p->pattern); -} -#else -#define looking_at(a,b) do {} while(0) -#endif - static struct grep_expr *compile_pattern_expr(struct grep_pat **); static struct grep_expr *compile_pattern_atom(struct grep_pat **list) { struct grep_pat *p; struct grep_expr *x; - looking_at("atom", list); - p = *list; switch (p->token) { case GREP_PATTERN: /* atom */ @@ -255,8 +208,6 @@ static struct grep_expr *compile_pattern_not(struct grep_pat **list) struct grep_pat *p; struct grep_expr *x; - looking_at("not", list); - p = *list; switch (p->token) { case GREP_NOT: @@ -279,8 +230,6 @@ static struct grep_expr *compile_pattern_and(struct grep_pat **list) struct grep_pat *p; struct grep_expr *x, *y, *z; - looking_at("and", list); - x = compile_pattern_not(list); p = *list; if (p && p->token == GREP_AND) { @@ -304,8 +253,6 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) struct grep_pat *p; struct grep_expr *x, *y, *z; - looking_at("or", list); - x = compile_pattern_and(list); p = *list; if (x && p && p->token != GREP_CLOSE_PAREN) { @@ -323,8 +270,6 @@ static struct grep_expr *compile_pattern_or(struct grep_pat **list) static struct grep_expr *compile_pattern_expr(struct grep_pat **list) { - looking_at("expr", list); - return compile_pattern_or(list); } @@ -388,9 +333,7 @@ static int buffer_is_binary(const char *ptr, unsigned long size) { if (FIRST_FEW_BYTES < size) size = FIRST_FEW_BYTES; - if (memchr(ptr, 0, size)) - return 1; - return 0; + return !!memchr(ptr, 0, size); } static int fixmatch(const char *pattern, char *line, regmatch_t *match) @@ -632,19 +575,40 @@ static int grep_buffer(struct grep_opt *opt, const char *name, return !!last_hit; } -static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name) +static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1, const char *name, int tree_name_len) { unsigned long size; char *data; char type[20]; + char *to_free = NULL; int hit; + data = read_sha1_file(sha1, type, &size); if (!data) { error("'%s': unable to read %s", name, sha1_to_hex(sha1)); return 0; } + if (opt->relative && opt->prefix_length) { + static char name_buf[PATH_MAX]; + char *cp; + int name_len = strlen(name) - opt->prefix_length + 1; + + if (!tree_name_len) + name += opt->prefix_length; + else { + if (ARRAY_SIZE(name_buf) <= name_len) + cp = to_free = xmalloc(name_len); + else + cp = name_buf; + memcpy(cp, name, tree_name_len); + strcpy(cp + tree_name_len, + name + tree_name_len + opt->prefix_length); + name = cp; + } + } hit = grep_buffer(opt, name, data, size); free(data); + free(to_free); return hit; } @@ -674,6 +638,8 @@ static int grep_file(struct grep_opt *opt, const char *filename) return 0; } close(i); + if (opt->relative && opt->prefix_length) + filename += opt->prefix_length; i = grep_buffer(opt, filename, data, st.st_size); free(data); return i; @@ -720,7 +686,7 @@ static int external_grep(struct grep_opt *opt, const char **paths, int cached) char *argptr = randarg; struct grep_pat *p; - if (opt->extended) + if (opt->extended || (opt->relative && opt->prefix_length)) return -1; len = nr = 0; push_arg("grep"); @@ -845,7 +811,7 @@ static int grep_cache(struct grep_opt *opt, const char **paths, int cached) if (!pathspec_matches(paths, ce->name)) continue; if (cached) - hit |= grep_sha1(opt, ce->sha1, ce->name); + hit |= grep_sha1(opt, ce->sha1, ce->name, 0); else hit |= grep_file(opt, ce->name); } @@ -860,11 +826,12 @@ static int grep_tree(struct grep_opt *opt, const char **paths, int hit = 0; struct name_entry entry; char *down; - char *path_buf = xmalloc(PATH_MAX + strlen(tree_name) + 100); + int tn_len = strlen(tree_name); + char *path_buf = xmalloc(PATH_MAX + tn_len + 100); - if (tree_name[0]) { - int offset = sprintf(path_buf, "%s:", tree_name); - down = path_buf + offset; + if (tn_len) { + tn_len = sprintf(path_buf, "%s:", tree_name); + down = path_buf + tn_len; strcat(down, base); } else { @@ -886,7 +853,7 @@ static int grep_tree(struct grep_opt *opt, const char **paths, if (!pathspec_matches(paths, down)) ; else if (S_ISREG(entry.mode)) - hit |= grep_sha1(opt, entry.sha1, path_buf); + hit |= grep_sha1(opt, entry.sha1, path_buf, tn_len); else if (S_ISDIR(entry.mode)) { char type[20]; struct tree_desc sub; @@ -907,7 +874,7 @@ static int grep_object(struct grep_opt *opt, const char **paths, struct object *obj, const char *name) { if (obj->type == OBJ_BLOB) - return grep_sha1(opt, obj->sha1, name); + return grep_sha1(opt, obj->sha1, name, 0); if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) { struct tree_desc tree; void *data; @@ -945,6 +912,8 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; memset(&opt, 0, sizeof(opt)); + opt.prefix_length = (prefix && *prefix) ? strlen(prefix) : 0; + opt.relative = 1; opt.pattern_tail = &opt.pattern_list; opt.regflags = REG_NEWLINE; @@ -1118,6 +1087,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } die(emsg_missing_argument, arg); } + if (!strcmp("--full-name", arg)) { + opt.relative = 0; + continue; + } if (!strcmp("--", arg)) { /* later processing wants to have this at argv[1] */ argv--; @@ -1176,8 +1149,15 @@ int cmd_grep(int argc, const char **argv, const char *prefix) verify_filename(prefix, argv[j]); } - if (i < argc) + if (i < argc) { paths = get_pathspec(prefix, argv + i); + if (opt.prefix_length && opt.relative) { + /* Make sure we do not get outside of paths */ + for (i = 0; paths[i]; i++) + if (strncmp(prefix, paths[i], opt.prefix_length)) + die("git-grep: cannot generate relative filenames containing '..'"); + } + } else if (prefix) { paths = xcalloc(2, sizeof(const char *)); paths[0] = prefix; diff --git a/builtin-ls-files.c b/builtin-ls-files.c index 11386c432b..ad8c41e731 100644 --- a/builtin-ls-files.c +++ b/builtin-ls-files.c @@ -12,21 +12,22 @@ #include "dir.h" #include "builtin.h" -static int abbrev = 0; -static int show_deleted = 0; -static int show_cached = 0; -static int show_others = 0; -static int show_stage = 0; -static int show_unmerged = 0; -static int show_modified = 0; -static int show_killed = 0; -static int show_valid_bit = 0; +static int abbrev; +static int show_deleted; +static int show_cached; +static int show_others; +static int show_stage; +static int show_unmerged; +static int show_modified; +static int show_killed; +static int show_valid_bit; static int line_terminator = '\n'; -static int prefix_len = 0, prefix_offset = 0; -static const char **pathspec = NULL; -static int error_unmatch = 0; -static char *ps_matched = NULL; +static int prefix_len; +static int prefix_offset; +static const char **pathspec; +static int error_unmatch; +static char *ps_matched; static const char *tag_cached = ""; static const char *tag_unmerged = ""; diff --git a/builtin-ls-tree.c b/builtin-ls-tree.c index 261147fdbe..201defd934 100644 --- a/builtin-ls-tree.c +++ b/builtin-ls-tree.c @@ -14,10 +14,10 @@ static int line_termination = '\n'; #define LS_TREE_ONLY 2 #define LS_SHOW_TREES 4 #define LS_NAME_ONLY 8 -static int abbrev = 0; -static int ls_options = 0; +static int abbrev; +static int ls_options; static const char **pathspec; -static int chomp_prefix = 0; +static int chomp_prefix; static const char *ls_tree_prefix; static const char ls_tree_usage[] = diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index 24a4fc63b3..0c65f93145 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -16,8 +16,8 @@ static FILE *cmitmsg, *patchfile, *fin, *fout; -static int keep_subject = 0; -static const char *metainfo_charset = NULL; +static int keep_subject; +static const char *metainfo_charset; static char line[1000]; static char date[1000]; static char name[1000]; @@ -31,7 +31,7 @@ static char charset[256]; static char multipart_boundary[1000]; static int multipart_boundary_len; -static int patch_lines = 0; +static int patch_lines; static char *sanity_check(char *name, char *email) { diff --git a/name-rev.c b/builtin-name-rev.c index f92f14e32f..d44e782c99 100644 --- a/name-rev.c +++ b/builtin-name-rev.c @@ -1,4 +1,5 @@ #include <stdlib.h> +#include "builtin.h" #include "cache.h" #include "commit.h" #include "tag.h" @@ -74,7 +75,7 @@ copy_data: } } -static int tags_only = 0; +static int tags_only; static int name_ref(const char *path, const unsigned char *sha1) { @@ -126,12 +127,11 @@ static const char* get_rev_name(struct object *o) return buffer; } -int main(int argc, char **argv) +int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = { 0, 0, NULL }; int as_is = 0, all = 0, transform_stdin = 0; - setup_git_directory(); git_config(git_default_config); if (argc < 2) diff --git a/pack-objects.c b/builtin-pack-objects.c index 861c7f08ff..f19f0d6046 100644 --- a/pack-objects.c +++ b/builtin-pack-objects.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "object.h" #include "blob.h" @@ -52,17 +53,17 @@ struct object_entry { */ static unsigned char object_list_sha1[20]; -static int non_empty = 0; -static int no_reuse_delta = 0; -static int local = 0; -static int incremental = 0; +static int non_empty; +static int no_reuse_delta; +static int local; +static int incremental; static struct object_entry **sorted_by_sha, **sorted_by_type; -static struct object_entry *objects = NULL; -static int nr_objects = 0, nr_alloc = 0, nr_result = 0; +static struct object_entry *objects; +static int nr_objects, nr_alloc, nr_result; static const char *base_name; static unsigned char pack_file_sha1[20]; static int progress = 1; -static volatile sig_atomic_t progress_update = 0; +static volatile sig_atomic_t progress_update; static int window = 10; /* @@ -71,8 +72,8 @@ static int window = 10; * sorted_by_sha is also possible but this was easier to code and faster. * This hashtable is built after all the objects are seen. */ -static int *object_ix = NULL; -static int object_ix_hashsz = 0; +static int *object_ix; +static int object_ix_hashsz; /* * Pack index for existing packs give us easy access to the offsets into @@ -89,15 +90,15 @@ struct pack_revindex { struct packed_git *p; unsigned long *revindex; } *pack_revindex = NULL; -static int pack_revindex_hashsz = 0; +static int pack_revindex_hashsz; /* * stats */ -static int written = 0; -static int written_delta = 0; -static int reused = 0; -static int reused_delta = 0; +static int written; +static int written_delta; +static int reused; +static int reused_delta; static int pack_revindex_ix(struct packed_git *p) { @@ -269,6 +270,22 @@ static unsigned long write_object(struct sha1file *f, * and we do not need to deltify it. */ + if (!entry->in_pack && !entry->delta) { + unsigned char *map; + unsigned long mapsize; + map = map_sha1_file(entry->sha1, &mapsize); + if (map && !legacy_loose_object(map)) { + /* We can copy straight into the pack file */ + sha1write(f, map, mapsize); + munmap(map, mapsize); + written++; + reused++; + return mapsize; + } + if (map) + munmap(map, mapsize); + } + if (! to_reuse) { buf = read_sha1_file(entry->sha1, type, &size); if (!buf) @@ -424,7 +441,7 @@ static int locate_object_entry_hash(const unsigned char *sha1) memcpy(&ui, sha1, sizeof(unsigned int)); i = ui % object_ix_hashsz; while (0 < object_ix[i]) { - if (!memcmp(sha1, objects[object_ix[i]-1].sha1, 20)) + if (!hashcmp(sha1, objects[object_ix[i] - 1].sha1)) return i; if (++i == object_ix_hashsz) i = 0; @@ -590,7 +607,7 @@ static struct pbase_tree_cache *pbase_tree_get(const unsigned char *sha1) */ for (neigh = 0; neigh < 8; neigh++) { ent = pbase_tree_cache[my_ix]; - if (ent && !memcmp(ent->sha1, sha1, 20)) { + if (ent && !hashcmp(ent->sha1, sha1)) { ent->ref++; return ent; } @@ -772,7 +789,7 @@ static void add_preferred_base(unsigned char *sha1) return; for (it = pbase_tree; it; it = it->next) { - if (!memcmp(it->pcache.sha1, tree_sha1, 20)) { + if (!hashcmp(it->pcache.sha1, tree_sha1)) { free(data); return; } @@ -914,7 +931,7 @@ static struct object_entry **create_sorted_list(entry_sort_t sort) static int sha1_sort(const struct object_entry *a, const struct object_entry *b) { - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static struct object_entry **create_final_object_list(void) @@ -1226,7 +1243,7 @@ static int git_pack_config(const char *k, const char *v) return git_default_config(k, v); } -int main(int argc, char **argv) +int cmd_pack_objects(int argc, const char **argv, const char *prefix) { SHA_CTX ctx; char line[40 + 1 + PATH_MAX + 2]; @@ -1235,7 +1252,6 @@ int main(int argc, char **argv) int num_preferred_base = 0; int i; - setup_git_directory(); git_config(git_pack_config); progress = isatty(2); diff --git a/builtin-prune.c b/builtin-prune.c index 89ec7f1426..fc885ce55b 100644 --- a/builtin-prune.c +++ b/builtin-prune.c @@ -11,7 +11,7 @@ #include "cache-tree.h" static const char prune_usage[] = "git-prune [-n]"; -static int show_only = 0; +static int show_only; static struct rev_info revs; static int prune_object(char *path, const char *filename, const unsigned char *sha1) diff --git a/builtin-push.c b/builtin-push.c index 53bc378f73..2b5e6fa9ed 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -10,14 +10,14 @@ static const char push_usage[] = "git-push [--all] [--tags] [-f | --force] <repository> [<refspec>...]"; -static int all = 0, tags = 0, force = 0, thin = 1; -static const char *execute = NULL; +static int all, tags, force, thin = 1; +static const char *execute; #define BUF_SIZE (2084) static char buffer[BUF_SIZE]; -static const char **refspec = NULL; -static int refspec_nr = 0; +static const char **refspec; +static int refspec_nr; static void add_refspec(const char *ref) { @@ -32,10 +32,8 @@ static int expand_one_ref(const char *ref, const unsigned char *sha1) /* Ignore the "refs/" at the beginning of the refname */ ref += 5; - if (strncmp(ref, "tags/", 5)) - return 0; - - add_refspec(strdup(ref)); + if (!strncmp(ref, "tags/", 5)) + add_refspec(strdup(ref)); return 0; } diff --git a/builtin-read-tree.c b/builtin-read-tree.c index 71a7026df4..53087faf7a 100644 --- a/builtin-read-tree.c +++ b/builtin-read-tree.c @@ -3,418 +3,16 @@ * * Copyright (C) Linus Torvalds, 2005 */ -#define DBRT_DEBUG 1 #include "cache.h" - #include "object.h" #include "tree.h" #include "tree-walk.h" #include "cache-tree.h" -#include <sys/time.h> -#include <signal.h> +#include "unpack-trees.h" #include "builtin.h" -static int reset = 0; -static int merge = 0; -static int update = 0; -static int index_only = 0; -static int nontrivial_merge = 0; -static int trivial_merges_only = 0; -static int aggressive = 0; -static int verbose_update = 0; -static volatile int progress_update = 0; -static const char *prefix = NULL; - -static int head_idx = -1; -static int merge_size = 0; - -static struct object_list *trees = NULL; - -static struct cache_entry df_conflict_entry; - -struct tree_entry_list { - struct tree_entry_list *next; - unsigned directory : 1; - unsigned executable : 1; - unsigned symlink : 1; - unsigned int mode; - const char *name; - const unsigned char *sha1; -}; - -static struct tree_entry_list df_conflict_list; - -typedef int (*merge_fn_t)(struct cache_entry **src); - -static struct tree_entry_list *create_tree_entry_list(struct tree *tree) -{ - struct tree_desc desc; - struct name_entry one; - struct tree_entry_list *ret = NULL; - struct tree_entry_list **list_p = &ret; - - desc.buf = tree->buffer; - desc.size = tree->size; - - while (tree_entry(&desc, &one)) { - struct tree_entry_list *entry; - - entry = xmalloc(sizeof(struct tree_entry_list)); - entry->name = one.path; - entry->sha1 = one.sha1; - entry->mode = one.mode; - entry->directory = S_ISDIR(one.mode) != 0; - entry->executable = (one.mode & S_IXUSR) != 0; - entry->symlink = S_ISLNK(one.mode) != 0; - entry->next = NULL; - - *list_p = entry; - list_p = &entry->next; - } - return ret; -} - -static int entcmp(const char *name1, int dir1, const char *name2, int dir2) -{ - int len1 = strlen(name1); - int len2 = strlen(name2); - int len = len1 < len2 ? len1 : len2; - int ret = memcmp(name1, name2, len); - unsigned char c1, c2; - if (ret) - return ret; - c1 = name1[len]; - c2 = name2[len]; - if (!c1 && dir1) - c1 = '/'; - if (!c2 && dir2) - c2 = '/'; - ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; - if (c1 && c2 && !ret) - ret = len1 - len2; - return ret; -} - -static int unpack_trees_rec(struct tree_entry_list **posns, int len, - const char *base, merge_fn_t fn, int *indpos) -{ - int baselen = strlen(base); - int src_size = len + 1; - do { - int i; - const char *first; - int firstdir = 0; - int pathlen; - unsigned ce_size; - struct tree_entry_list **subposns; - struct cache_entry **src; - int any_files = 0; - int any_dirs = 0; - char *cache_name; - int ce_stage; - - /* Find the first name in the input. */ - - first = NULL; - cache_name = NULL; - - /* Check the cache */ - if (merge && *indpos < active_nr) { - /* This is a bit tricky: */ - /* If the index has a subdirectory (with - * contents) as the first name, it'll get a - * filename like "foo/bar". But that's after - * "foo", so the entry in trees will get - * handled first, at which point we'll go into - * "foo", and deal with "bar" from the index, - * because the base will be "foo/". The only - * way we can actually have "foo/bar" first of - * all the things is if the trees don't - * contain "foo" at all, in which case we'll - * handle "foo/bar" without going into the - * directory, but that's fine (and will return - * an error anyway, with the added unknown - * file case. - */ - - cache_name = active_cache[*indpos]->name; - if (strlen(cache_name) > baselen && - !memcmp(cache_name, base, baselen)) { - cache_name += baselen; - first = cache_name; - } else { - cache_name = NULL; - } - } - -#if DBRT_DEBUG > 1 - if (first) - printf("index %s\n", first); -#endif - for (i = 0; i < len; i++) { - if (!posns[i] || posns[i] == &df_conflict_list) - continue; -#if DBRT_DEBUG > 1 - printf("%d %s\n", i + 1, posns[i]->name); -#endif - if (!first || entcmp(first, firstdir, - posns[i]->name, - posns[i]->directory) > 0) { - first = posns[i]->name; - firstdir = posns[i]->directory; - } - } - /* No name means we're done */ - if (!first) - return 0; - - pathlen = strlen(first); - ce_size = cache_entry_size(baselen + pathlen); - - src = xcalloc(src_size, sizeof(struct cache_entry *)); - - subposns = xcalloc(len, sizeof(struct tree_list_entry *)); - - if (cache_name && !strcmp(cache_name, first)) { - any_files = 1; - src[0] = active_cache[*indpos]; - remove_cache_entry_at(*indpos); - } - - for (i = 0; i < len; i++) { - struct cache_entry *ce; - - if (!posns[i] || - (posns[i] != &df_conflict_list && - strcmp(first, posns[i]->name))) { - continue; - } - - if (posns[i] == &df_conflict_list) { - src[i + merge] = &df_conflict_entry; - continue; - } - - if (posns[i]->directory) { - struct tree *tree = lookup_tree(posns[i]->sha1); - any_dirs = 1; - parse_tree(tree); - subposns[i] = create_tree_entry_list(tree); - posns[i] = posns[i]->next; - src[i + merge] = &df_conflict_entry; - continue; - } - - if (!merge) - ce_stage = 0; - else if (i + 1 < head_idx) - ce_stage = 1; - else if (i + 1 > head_idx) - ce_stage = 3; - else - ce_stage = 2; - - ce = xcalloc(1, ce_size); - ce->ce_mode = create_ce_mode(posns[i]->mode); - ce->ce_flags = create_ce_flags(baselen + pathlen, - ce_stage); - memcpy(ce->name, base, baselen); - memcpy(ce->name + baselen, first, pathlen + 1); - - any_files = 1; - - memcpy(ce->sha1, posns[i]->sha1, 20); - src[i + merge] = ce; - subposns[i] = &df_conflict_list; - posns[i] = posns[i]->next; - } - if (any_files) { - if (merge) { - int ret; - -#if DBRT_DEBUG > 1 - printf("%s:\n", first); - for (i = 0; i < src_size; i++) { - printf(" %d ", i); - if (src[i]) - printf("%s\n", sha1_to_hex(src[i]->sha1)); - else - printf("\n"); - } -#endif - ret = fn(src); - -#if DBRT_DEBUG > 1 - printf("Added %d entries\n", ret); -#endif - *indpos += ret; - } else { - for (i = 0; i < src_size; i++) { - if (src[i]) { - add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); - } - } - } - } - if (any_dirs) { - char *newbase = xmalloc(baselen + 2 + pathlen); - memcpy(newbase, base, baselen); - memcpy(newbase + baselen, first, pathlen); - newbase[baselen + pathlen] = '/'; - newbase[baselen + pathlen + 1] = '\0'; - if (unpack_trees_rec(subposns, len, newbase, fn, - indpos)) - return -1; - free(newbase); - } - free(subposns); - free(src); - } while (1); -} - -static void reject_merge(struct cache_entry *ce) -{ - die("Entry '%s' would be overwritten by merge. Cannot merge.", - ce->name); -} - -/* Unlink the last component and attempt to remove leading - * directories, in case this unlink is the removal of the - * last entry in the directory -- empty directories are removed. - */ -static void unlink_entry(char *name) -{ - char *cp, *prev; - - if (unlink(name)) - return; - prev = NULL; - while (1) { - int status; - cp = strrchr(name, '/'); - if (prev) - *prev = '/'; - if (!cp) - break; - - *cp = 0; - status = rmdir(name); - if (status) { - *cp = '/'; - break; - } - prev = cp; - } -} - -static void progress_interval(int signum) -{ - progress_update = 1; -} - -static void setup_progress_signal(void) -{ - struct sigaction sa; - struct itimerval v; - - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = progress_interval; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - v.it_interval.tv_sec = 1; - v.it_interval.tv_usec = 0; - v.it_value = v.it_interval; - setitimer(ITIMER_REAL, &v, NULL); -} - -static struct checkout state; -static void check_updates(struct cache_entry **src, int nr) -{ - unsigned short mask = htons(CE_UPDATE); - unsigned last_percent = 200, cnt = 0, total = 0; - - if (update && verbose_update) { - for (total = cnt = 0; cnt < nr; cnt++) { - struct cache_entry *ce = src[cnt]; - if (!ce->ce_mode || ce->ce_flags & mask) - total++; - } - - /* Don't bother doing this for very small updates */ - if (total < 250) - total = 0; - - if (total) { - fprintf(stderr, "Checking files out...\n"); - setup_progress_signal(); - progress_update = 1; - } - cnt = 0; - } - - while (nr--) { - struct cache_entry *ce = *src++; - - if (total) { - if (!ce->ce_mode || ce->ce_flags & mask) { - unsigned percent; - cnt++; - percent = (cnt * 100) / total; - if (percent != last_percent || - progress_update) { - fprintf(stderr, "%4u%% (%u/%u) done\r", - percent, cnt, total); - last_percent = percent; - progress_update = 0; - } - } - } - if (!ce->ce_mode) { - if (update) - unlink_entry(ce->name); - continue; - } - if (ce->ce_flags & mask) { - ce->ce_flags &= ~mask; - if (update) - checkout_entry(ce, &state, NULL); - } - } - if (total) { - signal(SIGALRM, SIG_IGN); - fputc('\n', stderr); - } -} - -static int unpack_trees(merge_fn_t fn) -{ - int indpos = 0; - unsigned len = object_list_length(trees); - struct tree_entry_list **posns; - int i; - struct object_list *posn = trees; - merge_size = len; - - if (len) { - posns = xmalloc(len * sizeof(struct tree_entry_list *)); - for (i = 0; i < len; i++) { - posns[i] = create_tree_entry_list((struct tree *) posn->item); - posn = posn->next; - } - if (unpack_trees_rec(posns, len, prefix ? prefix : "", - fn, &indpos)) - return -1; - } - - if (trivial_merges_only && nontrivial_merge) - die("Merge requires file-level merging"); - - check_updates(active_cache, active_nr); - return 0; -} +static struct object_list *trees; static int list_tree(unsigned char *sha1) { @@ -425,386 +23,6 @@ static int list_tree(unsigned char *sha1) return 0; } -static int same(struct cache_entry *a, struct cache_entry *b) -{ - if (!!a != !!b) - return 0; - if (!a && !b) - return 1; - return a->ce_mode == b->ce_mode && - !memcmp(a->sha1, b->sha1, 20); -} - - -/* - * When a CE gets turned into an unmerged entry, we - * want it to be up-to-date - */ -static void verify_uptodate(struct cache_entry *ce) -{ - struct stat st; - - if (index_only || reset) - return; - - if (!lstat(ce->name, &st)) { - unsigned changed = ce_match_stat(ce, &st, 1); - if (!changed) - return; - errno = 0; - } - if (reset) { - ce->ce_flags |= htons(CE_UPDATE); - return; - } - if (errno == ENOENT) - return; - die("Entry '%s' not uptodate. Cannot merge.", ce->name); -} - -static void invalidate_ce_path(struct cache_entry *ce) -{ - if (ce) - cache_tree_invalidate_path(active_cache_tree, ce->name); -} - -/* - * We do not want to remove or overwrite a working tree file that - * is not tracked. - */ -static void verify_absent(const char *path, const char *action) -{ - struct stat st; - - if (index_only || reset || !update) - return; - if (!lstat(path, &st)) - die("Untracked working tree file '%s' " - "would be %s by merge.", path, action); -} - -static int merged_entry(struct cache_entry *merge, struct cache_entry *old) -{ - merge->ce_flags |= htons(CE_UPDATE); - if (old) { - /* - * See if we can re-use the old CE directly? - * That way we get the uptodate stat info. - * - * This also removes the UPDATE flag on - * a match. - */ - if (same(old, merge)) { - *merge = *old; - } else { - verify_uptodate(old); - invalidate_ce_path(old); - } - } - else { - verify_absent(merge->name, "overwritten"); - invalidate_ce_path(merge); - } - - merge->ce_flags &= ~htons(CE_STAGEMASK); - add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - return 1; -} - -static int deleted_entry(struct cache_entry *ce, struct cache_entry *old) -{ - if (old) - verify_uptodate(old); - else - verify_absent(ce->name, "removed"); - ce->ce_mode = 0; - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); - invalidate_ce_path(ce); - return 1; -} - -static int keep_entry(struct cache_entry *ce) -{ - add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); - return 1; -} - -#if DBRT_DEBUG -static void show_stage_entry(FILE *o, - const char *label, const struct cache_entry *ce) -{ - if (!ce) - fprintf(o, "%s (missing)\n", label); - else - fprintf(o, "%s%06o %s %d\t%s\n", - label, - ntohl(ce->ce_mode), - sha1_to_hex(ce->sha1), - ce_stage(ce), - ce->name); -} -#endif - -static int threeway_merge(struct cache_entry **stages) -{ - struct cache_entry *index; - struct cache_entry *head; - struct cache_entry *remote = stages[head_idx + 1]; - int count; - int head_match = 0; - int remote_match = 0; - const char *path = NULL; - - int df_conflict_head = 0; - int df_conflict_remote = 0; - - int any_anc_missing = 0; - int no_anc_exists = 1; - int i; - - for (i = 1; i < head_idx; i++) { - if (!stages[i]) - any_anc_missing = 1; - else { - if (!path) - path = stages[i]->name; - no_anc_exists = 0; - } - } - - index = stages[0]; - head = stages[head_idx]; - - if (head == &df_conflict_entry) { - df_conflict_head = 1; - head = NULL; - } - - if (remote == &df_conflict_entry) { - df_conflict_remote = 1; - remote = NULL; - } - - if (!path && index) - path = index->name; - if (!path && head) - path = head->name; - if (!path && remote) - path = remote->name; - - /* First, if there's a #16 situation, note that to prevent #13 - * and #14. - */ - if (!same(remote, head)) { - for (i = 1; i < head_idx; i++) { - if (same(stages[i], head)) { - head_match = i; - } - if (same(stages[i], remote)) { - remote_match = i; - } - } - } - - /* We start with cases where the index is allowed to match - * something other than the head: #14(ALT) and #2ALT, where it - * is permitted to match the result instead. - */ - /* #14, #14ALT, #2ALT */ - if (remote && !df_conflict_head && head_match && !remote_match) { - if (index && !same(index, remote) && !same(index, head)) - reject_merge(index); - return merged_entry(remote, index); - } - /* - * If we have an entry in the index cache, then we want to - * make sure that it matches head. - */ - if (index && !same(index, head)) { - reject_merge(index); - } - - if (head) { - /* #5ALT, #15 */ - if (same(head, remote)) - return merged_entry(head, index); - /* #13, #3ALT */ - if (!df_conflict_remote && remote_match && !head_match) - return merged_entry(head, index); - } - - /* #1 */ - if (!head && !remote && any_anc_missing) - return 0; - - /* Under the new "aggressive" rule, we resolve mostly trivial - * cases that we historically had git-merge-one-file resolve. - */ - if (aggressive) { - int head_deleted = !head && !df_conflict_head; - int remote_deleted = !remote && !df_conflict_remote; - /* - * Deleted in both. - * Deleted in one and unchanged in the other. - */ - if ((head_deleted && remote_deleted) || - (head_deleted && remote && remote_match) || - (remote_deleted && head && head_match)) { - if (index) - return deleted_entry(index, index); - else if (path) - verify_absent(path, "removed"); - return 0; - } - /* - * Added in both, identically. - */ - if (no_anc_exists && head && remote && same(head, remote)) - return merged_entry(head, index); - - } - - /* Below are "no merge" cases, which require that the index be - * up-to-date to avoid the files getting overwritten with - * conflict resolution files. - */ - if (index) { - verify_uptodate(index); - } - else if (path) - verify_absent(path, "overwritten"); - - nontrivial_merge = 1; - - /* #2, #3, #4, #6, #7, #9, #11. */ - count = 0; - if (!head_match || !remote_match) { - for (i = 1; i < head_idx; i++) { - if (stages[i]) { - keep_entry(stages[i]); - count++; - break; - } - } - } -#if DBRT_DEBUG - else { - fprintf(stderr, "read-tree: warning #16 detected\n"); - show_stage_entry(stderr, "head ", stages[head_match]); - show_stage_entry(stderr, "remote ", stages[remote_match]); - } -#endif - if (head) { count += keep_entry(head); } - if (remote) { count += keep_entry(remote); } - return count; -} - -/* - * Two-way merge. - * - * The rule is to "carry forward" what is in the index without losing - * information across a "fast forward", favoring a successful merge - * over a merge failure when it makes sense. For details of the - * "carry forward" rule, please see <Documentation/git-read-tree.txt>. - * - */ -static int twoway_merge(struct cache_entry **src) -{ - struct cache_entry *current = src[0]; - struct cache_entry *oldtree = src[1], *newtree = src[2]; - - if (merge_size != 2) - return error("Cannot do a twoway merge of %d trees", - merge_size); - - if (current) { - if ((!oldtree && !newtree) || /* 4 and 5 */ - (!oldtree && newtree && - same(current, newtree)) || /* 6 and 7 */ - (oldtree && newtree && - same(oldtree, newtree)) || /* 14 and 15 */ - (oldtree && newtree && - !same(oldtree, newtree) && /* 18 and 19*/ - same(current, newtree))) { - return keep_entry(current); - } - else if (oldtree && !newtree && same(current, oldtree)) { - /* 10 or 11 */ - return deleted_entry(oldtree, current); - } - else if (oldtree && newtree && - same(current, oldtree) && !same(current, newtree)) { - /* 20 or 21 */ - return merged_entry(newtree, current); - } - else { - /* all other failures */ - if (oldtree) - reject_merge(oldtree); - if (current) - reject_merge(current); - if (newtree) - reject_merge(newtree); - return -1; - } - } - else if (newtree) - return merged_entry(newtree, current); - else - return deleted_entry(oldtree, current); -} - -/* - * Bind merge. - * - * Keep the index entries at stage0, collapse stage1 but make sure - * stage0 does not have anything there. - */ -static int bind_merge(struct cache_entry **src) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (merge_size != 1) - return error("Cannot do a bind merge of %d trees\n", - merge_size); - if (a && old) - die("Entry '%s' overlaps. Cannot bind.", a->name); - if (!a) - return keep_entry(old); - else - return merged_entry(a, NULL); -} - -/* - * One-way merge. - * - * The rule is: - * - take the stat information from stage0, take the data from stage1 - */ -static int oneway_merge(struct cache_entry **src) -{ - struct cache_entry *old = src[0]; - struct cache_entry *a = src[1]; - - if (merge_size != 1) - return error("Cannot do a oneway merge of %d trees", - merge_size); - - if (!a) - return deleted_entry(old, old); - if (old && same(old, a)) { - if (reset) { - struct stat st; - if (lstat(old->name, &st) || - ce_match_stat(old, &st, 1)) - old->ce_flags |= htons(CE_UPDATE); - } - return keep_entry(old); - } - return merged_entry(a, old); -} - static int read_cache_unmerged(void) { int i; @@ -818,7 +36,7 @@ static int read_cache_unmerged(void) if (ce_stage(ce)) { if (last && !strcmp(ce->name, last->name)) continue; - invalidate_ce_path(ce); + cache_tree_invalidate_path(active_cache_tree, ce->name); last = ce; ce->ce_mode = 0; ce->ce_flags &= ~htons(CE_STAGEMASK); @@ -874,22 +92,18 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) { int i, newfd, stage = 0; unsigned char sha1[20]; - merge_fn_t fn = NULL; + struct unpack_trees_options opts; - df_conflict_list.next = &df_conflict_list; - state.base_dir = ""; - state.force = 1; - state.quiet = 1; - state.refresh_cache = 1; + memset(&opts, 0, sizeof(opts)); + opts.head_idx = -1; + setup_git_directory(); git_config(git_default_config); newfd = hold_lock_file_for_update(&lock_file, get_index_file(), 1); git_config(git_default_config); - merge = 0; - reset = 0; for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -897,12 +111,12 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * the working tree. */ if (!strcmp(arg, "-u")) { - update = 1; + opts.update = 1; continue; } if (!strcmp(arg, "-v")) { - verbose_update = 1; + opts.verbose_update = 1; continue; } @@ -910,7 +124,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * not even look at the working tree. */ if (!strcmp(arg, "-i")) { - index_only = 1; + opts.index_only = 1; continue; } @@ -919,10 +133,10 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * given subdirectory. */ if (!strncmp(arg, "--prefix=", 9)) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); - prefix = arg + 9; - merge = 1; + opts.prefix = arg + 9; + opts.merge = 1; stage = 1; if (read_cache_unmerged()) die("you need to resolve your current index first"); @@ -934,38 +148,38 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * correspond to them. */ if (!strcmp(arg, "--reset")) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); - reset = 1; - merge = 1; + opts.reset = 1; + opts.merge = 1; stage = 1; read_cache_unmerged(); continue; } if (!strcmp(arg, "--trivial")) { - trivial_merges_only = 1; + opts.trivial_merges_only = 1; continue; } if (!strcmp(arg, "--aggressive")) { - aggressive = 1; + opts.aggressive = 1; continue; } /* "-m" stands for "merge", meaning we start in stage 1 */ if (!strcmp(arg, "-m")) { - if (stage || merge || prefix) + if (stage || opts.merge || opts.prefix) usage(read_tree_usage); if (read_cache_unmerged()) die("you need to resolve your current index first"); stage = 1; - merge = 1; + opts.merge = 1; continue; } /* using -u and -i at the same time makes no sense */ - if (1 < index_only + update) + if (1 < opts.index_only + opts.update) usage(read_tree_usage); if (get_sha1(arg, sha1)) @@ -974,52 +188,53 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) die("failed to unpack tree object %s", arg); stage++; } - if ((update||index_only) && !merge) + if ((opts.update||opts.index_only) && !opts.merge) usage(read_tree_usage); - if (prefix) { - int pfxlen = strlen(prefix); + if (opts.prefix) { + int pfxlen = strlen(opts.prefix); int pos; - if (prefix[pfxlen-1] != '/') + if (opts.prefix[pfxlen-1] != '/') die("prefix must end with /"); if (stage != 2) die("binding merge takes only one tree"); - pos = cache_name_pos(prefix, pfxlen); + pos = cache_name_pos(opts.prefix, pfxlen); if (0 <= pos) die("corrupt index file"); pos = -pos-1; if (pos < active_nr && - !strncmp(active_cache[pos]->name, prefix, pfxlen)) - die("subdirectory '%s' already exists.", prefix); - pos = cache_name_pos(prefix, pfxlen-1); + !strncmp(active_cache[pos]->name, opts.prefix, pfxlen)) + die("subdirectory '%s' already exists.", opts.prefix); + pos = cache_name_pos(opts.prefix, pfxlen-1); if (0 <= pos) - die("file '%.*s' already exists.", pfxlen-1, prefix); + die("file '%.*s' already exists.", + pfxlen-1, opts.prefix); } - if (merge) { + if (opts.merge) { if (stage < 2) die("just how do you expect me to merge %d trees?", stage-1); switch (stage - 1) { case 1: - fn = prefix ? bind_merge : oneway_merge; + opts.fn = opts.prefix ? bind_merge : oneway_merge; break; case 2: - fn = twoway_merge; + opts.fn = twoway_merge; break; case 3: default: - fn = threeway_merge; + opts.fn = threeway_merge; cache_tree_free(&active_cache_tree); break; } if (stage - 1 >= 3) - head_idx = stage - 2; + opts.head_idx = stage - 2; else - head_idx = 1; + opts.head_idx = 1; } - unpack_trees(fn); + unpack_trees(trees, &opts); /* * When reading only one tree (either the most basic form, @@ -1027,7 +242,7 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix) * valid cache-tree because the index must match exactly * what came from the tree. */ - if (trees && trees->item && !prefix && (!merge || (stage == 2))) { + if (trees && trees->item && !opts.prefix && (!opts.merge || (stage == 2))) { cache_tree_free(&active_cache_tree); prime_cache_tree(); } diff --git a/builtin-repo-config.c b/builtin-repo-config.c index c821e22717..c416480208 100644 --- a/builtin-repo-config.c +++ b/builtin-repo-config.c @@ -5,14 +5,14 @@ static const char git_config_set_usage[] = "git-repo-config [ --bool | --int ] [--get | --get-all | --get-regexp | --replace-all | --unset | --unset-all] name [value [value_regex]] | --list"; -static char* key = NULL; -static regex_t* key_regexp = NULL; -static regex_t* regexp = NULL; -static int show_keys = 0; -static int use_key_regexp = 0; -static int do_all = 0; -static int do_not_match = 0; -static int seen = 0; +static char *key; +static regex_t *key_regexp; +static regex_t *regexp; +static int show_keys; +static int use_key_regexp; +static int do_all; +static int do_not_match; +static int seen; static enum { T_RAW, T_INT, T_BOOL } type = T_RAW; static int show_all_config(const char *key_, const char *value_) diff --git a/builtin-rev-list.c b/builtin-rev-list.c index 0dee1734a3..bc48a3e230 100644 --- a/builtin-rev-list.c +++ b/builtin-rev-list.c @@ -39,9 +39,9 @@ static const char rev_list_usage[] = static struct rev_info revs; -static int bisect_list = 0; -static int show_timestamp = 0; -static int hdr_termination = 0; +static int bisect_list; +static int show_timestamp; +static int hdr_termination; static const char *header_prefix; static void show_commit(struct commit *commit) diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c index aca4a36032..fd3ccc8546 100644 --- a/builtin-rev-parse.c +++ b/builtin-rev-parse.c @@ -15,16 +15,16 @@ #define DO_NONFLAGS 8 static int filter = ~0; -static const char *def = NULL; +static const char *def; #define NORMAL 0 #define REVERSED 1 static int show_type = NORMAL; -static int symbolic = 0; -static int abbrev = 0; -static int output_sq = 0; +static int symbolic; +static int abbrev; +static int output_sq; -static int revs_count = 0; +static int revs_count; /* * Some arguments are relevant "revision" arguments, diff --git a/builtin-show-branch.c b/builtin-show-branch.c index 2a1b848f6c..18786f88e3 100644 --- a/builtin-show-branch.c +++ b/builtin-show-branch.c @@ -8,9 +8,9 @@ static const char show_branch_usage[] = "git-show-branch [--sparse] [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; -static int default_num = 0; -static int default_alloc = 0; -static const char **default_arg = NULL; +static int default_num; +static int default_alloc; +static const char **default_arg; #define UNINTERESTING 01 @@ -378,7 +378,7 @@ static int append_head_ref(const char *refname, const unsigned char *sha1) /* If both heads/foo and tags/foo exists, get_sha1 would * get confused. */ - if (get_sha1(refname + ofs, tmp) || memcmp(tmp, sha1, 20)) + if (get_sha1(refname + ofs, tmp) || hashcmp(tmp, sha1)) ofs = 5; return append_ref(refname + ofs, sha1); } @@ -442,7 +442,7 @@ static int rev_is_head(char *head_path, int headlen, char *name, { int namelen; if ((!head_path[0]) || - (head_sha1 && sha1 && memcmp(head_sha1, sha1, 20))) + (head_sha1 && sha1 && hashcmp(head_sha1, sha1))) return 0; namelen = strlen(name); if ((headlen < namelen) || diff --git a/symbolic-ref.c b/builtin-symbolic-ref.c index 193c87c174..b4ec6f28ed 100644 --- a/symbolic-ref.c +++ b/builtin-symbolic-ref.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" static const char git_symbolic_ref_usage[] = @@ -17,9 +18,8 @@ static void check_symref(const char *HEAD) die("No such ref: %s", HEAD); } -int main(int argc, const char **argv) +int cmd_symbolic_ref(int argc, const char **argv, const char *prefix) { - setup_git_directory(); git_config(git_default_config); switch (argc) { case 2: diff --git a/unpack-objects.c b/builtin-unpack-objects.c index 48c1ee7968..f0ae5c987a 100644 --- a/unpack-objects.c +++ b/builtin-unpack-objects.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "object.h" #include "delta.h" @@ -112,7 +113,7 @@ static void write_object(void *buf, unsigned long size, const char *type) } static int resolve_delta(const char *type, - void *base, unsigned long base_size, + void *base, unsigned long base_size, void *delta, unsigned long delta_size) { void *result; @@ -135,7 +136,7 @@ static void added_object(unsigned char *sha1, const char *type, void *data, unsi struct delta_info *info; while ((info = *p) != NULL) { - if (!memcmp(info->base_sha1, sha1, 20)) { + if (!hashcmp(info->base_sha1, sha1)) { *p = info->next; p = &delta_list; resolve_delta(type, data, size, info->delta, info->size); @@ -260,12 +261,12 @@ static void unpack_all(void) die("unresolved deltas left after unpacking"); } -int main(int argc, char **argv) +int cmd_unpack_objects(int argc, const char **argv, const char *prefix) { int i; unsigned char sha1[20]; - setup_git_directory(); + git_config(git_default_config); quiet = !isatty(2); @@ -291,7 +292,7 @@ int main(int argc, char **argv) unpack_all(); SHA1_Update(&ctx, buffer, offset); SHA1_Final(sha1, &ctx); - if (memcmp(fill(20), sha1, 20)) + if (hashcmp(fill(20), sha1)) die("final sha1 did not match"); use(20); diff --git a/builtin-update-index.c b/builtin-update-index.c index d2556f376b..5dd91af180 100644 --- a/builtin-update-index.c +++ b/builtin-update-index.c @@ -23,7 +23,7 @@ static int allow_replace; static int info_only; static int force_remove; static int verbose; -static int mark_valid_only = 0; +static int mark_valid_only; #define MARK_VALID 1 #define UNMARK_VALID 2 @@ -378,7 +378,7 @@ static int unresolve_one(const char *path) ret = -1; goto free_return; } - if (!memcmp(ce_2->sha1, ce_3->sha1, 20) && + if (!hashcmp(ce_2->sha1, ce_3->sha1) && ce_2->ce_mode == ce_3->ce_mode) { fprintf(stderr, "%s: identical in both, skipping.\n", path); @@ -460,7 +460,7 @@ static int do_reupdate(int ac, const char **av, old = read_one_ent(NULL, head_sha1, ce->name, ce_namelen(ce), 0); if (old && ce->ce_mode == old->ce_mode && - !memcmp(ce->sha1, old->sha1, 20)) { + !hashcmp(ce->sha1, old->sha1)) { free(old); continue; /* unchanged */ } diff --git a/verify-pack.c b/builtin-verify-pack.c index 357970da39..7d39d9bcd1 100644 --- a/verify-pack.c +++ b/builtin-verify-pack.c @@ -1,3 +1,4 @@ +#include "builtin.h" #include "cache.h" #include "pack.h" @@ -47,28 +48,28 @@ static int verify_one_pack(const char *path, int verbose) static const char verify_pack_usage[] = "git-verify-pack [-v] <pack>..."; -int main(int ac, char **av) +int cmd_verify_pack(int argc, const char **argv, const char *prefix) { int err = 0; int verbose = 0; int no_more_options = 0; int nothing_done = 1; - while (1 < ac) { - if (!no_more_options && av[1][0] == '-') { - if (!strcmp("-v", av[1])) + while (1 < argc) { + if (!no_more_options && argv[1][0] == '-') { + if (!strcmp("-v", argv[1])) verbose = 1; - else if (!strcmp("--", av[1])) + else if (!strcmp("--", argv[1])) no_more_options = 1; else usage(verify_pack_usage); } else { - if (verify_one_pack(av[1], verbose)) + if (verify_one_pack(argv[1], verbose)) err = 1; nothing_done = 0; } - ac--; av++; + argc--; argv++; } if (nothing_done) @@ -8,57 +8,57 @@ extern const char git_version_string[]; extern const char git_usage_string[]; extern void help_unknown_cmd(const char *cmd); +extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); +extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); +extern void stripspace(FILE *in, FILE *out); +extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); -extern int cmd_help(int argc, const char **argv, const char *prefix); -extern int cmd_version(int argc, const char **argv, const char *prefix); - -extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); -extern int cmd_show(int argc, const char **argv, const char *prefix); -extern int cmd_log(int argc, const char **argv, const char *prefix); -extern int cmd_diff(int argc, const char **argv, const char *prefix); -extern int cmd_format_patch(int argc, const char **argv, const char *prefix); -extern int cmd_count_objects(int argc, const char **argv, const char *prefix); - -extern int cmd_prune(int argc, const char **argv, const char *prefix); -extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); - -extern int cmd_push(int argc, const char **argv, const char *prefix); -extern int cmd_grep(int argc, const char **argv, const char *prefix); -extern int cmd_rm(int argc, const char **argv, const char *prefix); extern int cmd_add(int argc, const char **argv, const char *prefix); -extern int cmd_rev_list(int argc, const char **argv, const char *prefix); +extern int cmd_apply(int argc, const char **argv, const char *prefix); +extern int cmd_cat_file(int argc, const char **argv, const char *prefix); +extern int cmd_checkout_index(int argc, const char **argv, const char *prefix); extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix); -extern int cmd_init_db(int argc, const char **argv, const char *prefix); -extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); -extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); -extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); -extern int cmd_ls_files(int argc, const char **argv, const char *prefix); -extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); -extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_commit_tree(int argc, const char **argv, const char *prefix); -extern int cmd_apply(int argc, const char **argv, const char *prefix); -extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_count_objects(int argc, const char **argv, const char *prefix); extern int cmd_diff_files(int argc, const char **argv, const char *prefix); extern int cmd_diff_index(int argc, const char **argv, const char *prefix); +extern int cmd_diff(int argc, const char **argv, const char *prefix); extern int cmd_diff_stages(int argc, const char **argv, const char *prefix); extern int cmd_diff_tree(int argc, const char **argv, const char *prefix); -extern int cmd_cat_file(int argc, const char **argv, const char *prefix); -extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); -extern int cmd_update_index(int argc, const char **argv, const char *prefix); -extern int cmd_update_ref(int argc, const char **argv, const char *prefix); extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix); +extern int cmd_format_patch(int argc, const char **argv, const char *prefix); +extern int cmd_get_tar_commit_id(int argc, const char **argv, const char *prefix); +extern int cmd_grep(int argc, const char **argv, const char *prefix); +extern int cmd_help(int argc, const char **argv, const char *prefix); +extern int cmd_init_db(int argc, const char **argv, const char *prefix); +extern int cmd_log(int argc, const char **argv, const char *prefix); +extern int cmd_ls_files(int argc, const char **argv, const char *prefix); +extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); +extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); +extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); +extern int cmd_name_rev(int argc, const char **argv, const char *prefix); +extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_prune(int argc, const char **argv, const char *prefix); +extern int cmd_prune_packed(int argc, const char **argv, const char *prefix); +extern int cmd_push(int argc, const char **argv, const char *prefix); +extern int cmd_read_tree(int argc, const char **argv, const char *prefix); extern int cmd_repo_config(int argc, const char **argv, const char *prefix); - +extern int cmd_rev_list(int argc, const char **argv, const char *prefix); +extern int cmd_rev_parse(int argc, const char **argv, const char *prefix); +extern int cmd_rm(int argc, const char **argv, const char *prefix); +extern int cmd_show_branch(int argc, const char **argv, const char *prefix); +extern int cmd_show(int argc, const char **argv, const char *prefix); +extern int cmd_stripspace(int argc, const char **argv, const char *prefix); +extern int cmd_symbolic_ref(int argc, const char **argv, const char *prefix); +extern int cmd_tar_tree(int argc, const char **argv, const char *prefix); +extern int cmd_unpack_objects(int argc, const char **argv, const char *prefix); +extern int cmd_update_index(int argc, const char **argv, const char *prefix); +extern int cmd_update_ref(int argc, const char **argv, const char *prefix); +extern int cmd_upload_tar(int argc, const char **argv, const char *prefix); +extern int cmd_version(int argc, const char **argv, const char *prefix); +extern int cmd_whatchanged(int argc, const char **argv, const char *prefix); extern int cmd_write_tree(int argc, const char **argv, const char *prefix); -extern int write_tree(unsigned char *sha1, int missing_ok, const char *prefix); +extern int cmd_verify_pack(int argc, const char **argv, const char *prefix); -extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); -extern int split_mbox(const char **mbox, const char *dir, int allow_bare, int nr_prec, int skip); - -extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); -extern int mailinfo(FILE *in, FILE *out, int ks, const char *encoding, const char *msg, const char *patch); - -extern int cmd_stripspace(int argc, const char **argv, const char *prefix); -extern void stripspace(FILE *in, FILE *out); #endif @@ -210,6 +210,14 @@ extern char *sha1_pack_name(const unsigned char *sha1); extern char *sha1_pack_index_name(const unsigned char *sha1); extern const char *find_unique_abbrev(const unsigned char *sha1, int); extern const unsigned char null_sha1[20]; +static inline int is_null_sha1(const unsigned char *sha1) +{ + return !memcmp(sha1, null_sha1, 20); +} +static inline int hashcmp(const unsigned char *sha1, const unsigned char *sha2) +{ + return memcmp(sha1, sha2, 20); +} int git_mkstemp(char *path, size_t n, const char *template); @@ -244,6 +252,8 @@ extern int move_temp_to_file(const char *tmpfile, char *filename); extern int has_sha1_pack(const unsigned char *sha1); extern int has_sha1_file(const unsigned char *sha1); +extern void *map_sha1_file(const unsigned char *sha1, unsigned long *); +extern int legacy_loose_object(unsigned char *); extern int has_pack_file(const unsigned char *sha1); extern int has_pack_index(const unsigned char *sha1); diff --git a/check-racy.c b/check-racy.c new file mode 100644 index 0000000000..d6a08b4a55 --- /dev/null +++ b/check-racy.c @@ -0,0 +1,28 @@ +#include "cache.h" + +int main(int ac, char **av) +{ + int i; + int dirty, clean, racy; + + dirty = clean = racy = 0; + read_cache(); + for (i = 0; i < active_nr; i++) { + struct cache_entry *ce = active_cache[i]; + struct stat st; + + if (lstat(ce->name, &st)) { + error("lstat(%s): %s", ce->name, strerror(errno)); + continue; + } + + if (ce_match_stat(ce, &st, 0)) + dirty++; + else if (ce_match_stat(ce, &st, 2)) + racy++; + else + clean++; + } + printf("dirty %d, clean %d, racy %d\n", dirty, clean, racy); + return 0; +} diff --git a/combine-diff.c b/combine-diff.c index ba8baca0ab..0682acd50d 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -7,13 +7,6 @@ #include "xdiff-interface.h" #include "log-tree.h" -static int uninteresting(struct diff_filepair *p) -{ - if (diff_unmodified_pair(p)) - return 1; - return 0; -} - static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent) { struct diff_queue_struct *q = &diff_queued_diff; @@ -25,7 +18,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, for (i = 0; i < q->nr; i++) { int len; const char *path; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); @@ -57,7 +50,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, const char *path; int len; - if (uninteresting(q->queue[i])) + if (diff_unmodified_pair(q->queue[i])) continue; path = q->queue[i]->two->path; len = strlen(path); @@ -101,7 +94,7 @@ static char *grab_blob(const unsigned char *sha1, unsigned long *size) { char *blob; char type[20]; - if (!memcmp(sha1, null_sha1, 20)) { + if (is_null_sha1(sha1)) { /* deleted blob */ *size = 0; return xcalloc(1, 1); @@ -609,16 +602,16 @@ static void dump_quoted_path(const char *prefix, const char *path, printf("%s\n", c_reset); } -static int show_patch_diff(struct combine_diff_path *elem, int num_parent, - int dense, struct rev_info *rev) +static void show_patch_diff(struct combine_diff_path *elem, int num_parent, + int dense, struct rev_info *rev) { struct diff_options *opt = &rev->diffopt; unsigned long result_size, cnt, lno; char *result, *cp; struct sline *sline; /* survived lines */ int mode_differs = 0; - int i, show_hunks, shown_header = 0; - int working_tree_file = !memcmp(elem->sha1, null_sha1, 20); + int i, show_hunks; + int working_tree_file = is_null_sha1(elem->sha1); int abbrev = opt->full_index ? 40 : DEFAULT_ABBREV; mmfile_t result_file; @@ -695,8 +688,8 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, for (i = 0; i < num_parent; i++) { int j; for (j = 0; j < i; j++) { - if (!memcmp(elem->parent[i].sha1, - elem->parent[j].sha1, 20)) { + if (!hashcmp(elem->parent[i].sha1, + elem->parent[j].sha1)) { reuse_combine_diff(sline, cnt, i, j); break; } @@ -769,7 +762,6 @@ static int show_patch_diff(struct combine_diff_path *elem, int num_parent, } free(sline[0].p_lno); free(sline); - return shown_header; } #define COLONS "::::::::::::::::::::::::::::::::" @@ -837,11 +829,10 @@ void show_combined_diff(struct combine_diff_path *p, return; if (opt->output_format & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME | - DIFF_FORMAT_NAME_STATUS)) { + DIFF_FORMAT_NAME_STATUS)) show_raw_diff(p, num_parent, rev); - } else if (opt->output_format & DIFF_FORMAT_PATCH) { + else if (opt->output_format & DIFF_FORMAT_PATCH) show_patch_diff(p, num_parent, dense, rev); - } } void diff_tree_combined(const unsigned char *sha1, @@ -7,15 +7,15 @@ int save_commit_buffer = 1; struct sort_node { /* - * the number of children of the associated commit - * that also occur in the list being sorted. - */ + * the number of children of the associated commit + * that also occur in the list being sorted. + */ unsigned int indegree; /* - * reference to original list item that we will re-use - * on output. - */ + * reference to original list item that we will re-use + * on output. + */ struct commit_list * list_item; }; @@ -123,7 +123,7 @@ static int commit_graft_pos(const unsigned char *sha1) while (lo < hi) { int mi = (lo + hi) / 2; struct commit_graft *graft = commit_graft[mi]; - int cmp = memcmp(sha1, graft->sha1, 20); + int cmp = hashcmp(sha1, graft->sha1); if (!cmp) return mi; if (cmp < 0) @@ -727,10 +727,10 @@ struct commit *pop_commit(struct commit_list **stack) int count_parents(struct commit * commit) { - int count = 0; + int count; struct commit_list * parents = commit->parents; - for (count=0;parents; parents=parents->next,count++) - ; + for (count = 0; parents; parents = parents->next,count++) + ; return count; } diff --git a/config.mak.in b/config.mak.in index 04f508ab90..369e6116e0 100644 --- a/config.mak.in +++ b/config.mak.in @@ -22,3 +22,19 @@ VPATH = @srcdir@ export exec_prefix mandir export srcdir VPATH +NO_PYTHON=@NO_PYTHON@ +NEEDS_SSL_WITH_CRYPTO=@NEEDS_SSL_WITH_CRYPTO@ +NO_OPENSSL=@NO_OPENSSL@ +NO_CURL=@NO_CURL@ +NO_EXPAT=@NO_EXPAT@ +NEEDS_LIBICONV=@NEEDS_LIBICONV@ +NEEDS_SOCKET=@NEEDS_SOCKET@ +NO_D_INO_IN_DIRENT=@NO_D_INO_IN_DIRENT@ +NO_D_TYPE_IN_DIRENT=@NO_D_TYPE_IN_DIRENT@ +NO_SOCKADDR_STORAGE=@NO_SOCKADDR_STORAGE@ +NO_IPV6=@NO_IPV6@ +NO_C99_FORMAT=@NO_C99_FORMAT@ +NO_STRCASESTR=@NO_STRCASESTR@ +NO_STRLCPY=@NO_STRLCPY@ +NO_SETENV=@NO_SETENV@ + diff --git a/configure.ac b/configure.ac index a9c88c6a4d..36f9cd94d8 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) -AC_INIT([git], [1.4.2], [git@vger.kernel.org]) +AC_INIT([git], [@@GIT_VERSION@@], [git@vger.kernel.org]) AC_CONFIG_SRCDIR([git.c]) @@ -19,6 +19,77 @@ echo "# ${config_append}. Generated by configure." > "${config_append}" # Append LINE to file ${config_append} AC_DEFUN([GIT_CONF_APPEND_LINE], [echo "$1" >> "${config_append}"])# GIT_CONF_APPEND_LINE +# +# GIT_ARG_SET_PATH(PROGRAM) +# ------------------------- +# Provide --with-PROGRAM=PATH option to set PATH to PROGRAM +AC_DEFUN([GIT_ARG_SET_PATH], +[AC_ARG_WITH([$1], + [AS_HELP_STRING([--with-$1=PATH], + [provide PATH to $1])], + [GIT_CONF_APPEND_PATH($1)],[]) +])# GIT_ARG_SET_PATH +# +# GIT_CONF_APPEND_PATH(PROGRAM) +# ------------------------------ +# Parse --with-PROGRAM=PATH option to set PROGRAM_PATH=PATH +# Used by GIT_ARG_SET_PATH(PROGRAM) +AC_DEFUN([GIT_CONF_APPEND_PATH], +[PROGRAM=m4_toupper($1); \ +if test "$withval" = "no"; then \ + AC_MSG_ERROR([You cannot use git without $1]); \ +else \ + if test "$withval" = "yes"; then \ + AC_MSG_WARN([You should provide path for --with-$1=PATH]); \ + else \ + GIT_CONF_APPEND_LINE(${PROGRAM}_PATH=$withval); \ + fi; \ +fi; \ +]) # GIT_CONF_APPEND_PATH +# +# GIT_PARSE_WITH(PACKAGE) +# ----------------------- +# For use in AC_ARG_WITH action-if-found, for packages default ON. +# * Set NO_PACKAGE=YesPlease for --without-PACKAGE +# * Set PACKAGEDIR=PATH for --with-PACKAGE=PATH +# * Unset NO_PACKAGE for --with-PACKAGE without ARG +AC_DEFUN([GIT_PARSE_WITH], +[PACKAGE=m4_toupper($1); \ +if test "$withval" = "no"; then \ + m4_toupper(NO_$1)=YesPlease; \ +elif test "$withval" = "yes"; then \ + m4_toupper(NO_$1)=; \ +else \ + m4_toupper(NO_$1)=; \ + GIT_CONF_APPEND_LINE(${PACKAGE}DIR=$withval); \ +fi \ +])# GIT_PARSE_WITH + + +## Site configuration related to programs (before tests) +## --with-PACKAGE[=ARG] and --without-PACKAGE +# +# Define SHELL_PATH to provide path to shell. +GIT_ARG_SET_PATH(shell) +# +# Define PERL_PATH to provide path to Perl. +GIT_ARG_SET_PATH(perl) +# +# Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. +AC_ARG_WITH(python,[AS_HELP_STRING([--with-python=PATH], [provide PATH to python]) +AS_HELP_STRING([--without-python], [don't use python scripts])], + [if test "$withval" = "no"; then \ + NO_PYTHON=YesPlease; \ + elif test "$withval" = "yes"; then \ + NO_PYTHON=; \ + else \ + NO_PYTHON=; \ + PYTHON_PATH=$withval; \ + fi; \ + ]) +AC_SUBST(NO_PYTHON) +AC_SUBST(PYTHON_PATH) ## Checks for programs. @@ -30,6 +101,16 @@ AC_CHECK_TOOL(AR, ar, :) AC_CHECK_PROGS(TAR, [gtar tar]) # # Define NO_PYTHON if you want to lose all benefits of the recursive merge. +# Define PYTHON_PATH to provide path to Python. +if test -z "$NO_PYTHON"; then + if test -z "$PYTHON_PATH"; then + AC_PATH_PROGS(PYTHON_PATH, [python python2.4 python2.3 python2]) + fi + if test -n "$PYTHON_PATH"; then + GIT_CONF_APPEND_LINE([PYTHON_PATH=@PYTHON_PATH@]) + NO_PYTHON="" + fi +fi ## Checks for libraries. @@ -37,32 +118,43 @@ AC_MSG_NOTICE([CHECKS for libraries]) # # Define NO_OPENSSL environment variable if you do not have OpenSSL. # Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin). -AC_CHECK_LIB([ssl], [SHA1_Init],[], -[AC_CHECK_LIB([crypto], [SHA1_INIT], - [GIT_CONF_APPEND_LINE(NEEDS_SSL_WITH_CRYPTO=YesPlease)], - [GIT_CONF_APPEND_LINE(NO_OPENSSL=YesPlease)])]) +AC_CHECK_LIB([crypto], [SHA1_Init], +[NEEDS_SSL_WITH_CRYPTO=], +[AC_CHECK_LIB([ssl], [SHA1_Init], + [NEEDS_SSL_WITH_CRYPTO=YesPlease + NEEDS_SSL_WITH_CRYPTO=], + [NO_OPENSSL=YesPlease])]) +AC_SUBST(NEEDS_SSL_WITH_CRYPTO) +AC_SUBST(NO_OPENSSL) # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. -AC_CHECK_LIB([curl], [curl_global_init],[], -[GIT_CONF_APPEND_LINE(NO_CURL=YesPlease)]) +AC_CHECK_LIB([curl], [curl_global_init], +[NO_CURL=], +[NO_CURL=YesPlease]) +AC_SUBST(NO_CURL) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. -AC_CHECK_LIB([expat], [XML_ParserCreate],[], -[GIT_CONF_APPEND_LINE(NO_EXPAT=YesPlease)]) +AC_CHECK_LIB([expat], [XML_ParserCreate], +[NO_EXPAT=], +[NO_EXPAT=YesPlease]) +AC_SUBST(NO_EXPAT) # # Define NEEDS_LIBICONV if linking with libc is not enough (Darwin). -AC_CHECK_LIB([c], [iconv],[], -[AC_CHECK_LIB([iconv],[iconv], - [GIT_CONF_APPEND_LINE(NEEDS_LIBICONV=YesPlease)],[])]) +AC_CHECK_LIB([c], [iconv], +[NEEDS_LIBICONV=], +[NEEDS_LIBICONV=YesPlease]) +AC_SUBST(NEEDS_LIBICONV) # # Define NEEDS_SOCKET if linking with libc is not enough (SunOS, # Patrick Mauritz). -AC_CHECK_LIB([c], [socket],[], -[AC_CHECK_LIB([socket],[socket], - [GIT_CONF_APPEND_LINE(NEEDS_SOCKET=YesPlease)],[])]) +AC_CHECK_LIB([c], [socket], +[NEEDS_SOCKET=], +[NEEDS_SOCKET=YesPlease]) +AC_SUBST(NEEDS_SOCKET) +test -n "$NEEDS_SOCKET" && LIBS="$LIBS -lsocket" ## Checks for header files. @@ -72,21 +164,65 @@ AC_CHECK_LIB([c], [socket],[], AC_MSG_NOTICE([CHECKS for typedefs, structures, and compiler characteristics]) # # Define NO_D_INO_IN_DIRENT if you don't have d_ino in your struct dirent. -AC_CHECK_MEMBER(struct dirent.d_ino,[], -[GIT_CONF_APPEND_LINE(NO_D_INO_IN_DIRENT=YesPlease)], +AC_CHECK_MEMBER(struct dirent.d_ino, +[NO_D_INO_IN_DIRENT=], +[NO_D_INO_IN_DIRENT=YesPlease], [#include <dirent.h>]) +AC_SUBST(NO_D_INO_IN_DIRENT) # # Define NO_D_TYPE_IN_DIRENT if your platform defines DT_UNKNOWN but lacks # d_type in struct dirent (latest Cygwin -- will be fixed soonish). -AC_CHECK_MEMBER(struct dirent.d_type,[], -[GIT_CONF_APPEND_LINE(NO_D_TYPE_IN_DIRENT=YesPlease)], +AC_CHECK_MEMBER(struct dirent.d_type, +[NO_D_TYPE_IN_DIRENT=], +[NO_D_TYPE_IN_DIRENT=YesPlease], [#include <dirent.h>]) +AC_SUBST(NO_D_TYPE_IN_DIRENT) # # Define NO_SOCKADDR_STORAGE if your platform does not have struct # sockaddr_storage. -AC_CHECK_TYPE(struct sockaddr_storage,[], -[GIT_CONF_APPEND_LINE(NO_SOCKADDR_STORAGE=YesPlease)], -[#include <netinet/in.h>]) +AC_CHECK_TYPE(struct sockaddr_storage, +[NO_SOCKADDR_STORAGE=], +[NO_SOCKADDR_STORAGE=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +]) +AC_SUBST(NO_SOCKADDR_STORAGE) +# +# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). +AC_CHECK_TYPE([struct addrinfo],[ + AC_CHECK_FUNC([getaddrinfo], + [NO_IPV6=], + [NO_IPV6=YesPlease]) +],[NO_IPV6=YesPlease],[ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +]) +AC_SUBST(NO_IPV6) +# +# 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. +AC_CACHE_CHECK(whether formatted IO functions support C99 size specifiers, + ac_cv_c_c99_format, +[# Actually git uses only %z (%zu) in alloc.c, and %t (%td) in mktag.c +AC_RUN_IFELSE( + [AC_LANG_PROGRAM([AC_INCLUDES_DEFAULT], + [[char buf[64]; + if (sprintf(buf, "%lld%hhd%jd%zd%td", (long long int)1, (char)2, (intmax_t)3, (size_t)4, (ptrdiff_t)5) != 5) + exit(1); + else if (strcmp(buf, "12345")) + exit(2);]])], + [ac_cv_c_c99_format=yes], + [ac_cv_c_c99_format=no]) +]) +if test $ac_cv_c_c99_format = no; then + NO_C99_FORMAT=YesPlease +else + NO_C99_FORMAT= +fi +AC_SUBST(NO_C99_FORMAT) ## Checks for library functions. @@ -94,21 +230,25 @@ AC_CHECK_TYPE(struct sockaddr_storage,[], AC_MSG_NOTICE([CHECKS for library functions]) # # Define NO_STRCASESTR if you don't have strcasestr. -AC_CHECK_FUNC(strcasestr,[], -[GIT_CONF_APPEND_LINE(NO_STRCASESTR=YesPlease)]) +AC_CHECK_FUNC(strcasestr, +[NO_STRCASESTR=], +[NO_STRCASESTR=YesPlease]) +AC_SUBST(NO_STRCASESTR) # # Define NO_STRLCPY if you don't have strlcpy. -AC_CHECK_FUNC(strlcpy,[], -[GIT_CONF_APPEND_LINE(NO_STRLCPY=YesPlease)]) +AC_CHECK_FUNC(strlcpy, +[NO_STRLCPY=], +[NO_STRLCPY=YesPlease]) +AC_SUBST(NO_STRLCPY) # # Define NO_SETENV if you don't have setenv in the C library. -AC_CHECK_FUNC(setenv,[], -[GIT_CONF_APPEND_LINE(NO_SETENV=YesPlease)]) +AC_CHECK_FUNC(setenv, +[NO_SETENV=], +[NO_SETENV=YesPlease]) +AC_SUBST(NO_SETENV) # # Define NO_MMAP if you want to avoid mmap. # -# Define NO_IPV6 if you lack IPv6 support and getaddrinfo(). -# # Define NO_ICONV if your libc does not properly support iconv. @@ -125,9 +265,11 @@ AC_CHECK_FUNC(setenv,[], # a missing newline at the end of the file. -## Site configuration +## Site configuration (override autodetection) ## --with-PACKAGE[=ARG] and --without-PACKAGE -# Define NO_SVN_TESTS if you want to skip time-consuming SVN interopability +AC_MSG_NOTICE([CHECKS for site configuration]) +# +# Define NO_SVN_TESTS if you want to skip time-consuming SVN interoperability # tests. These tests take up a significant amount of the total test time # but are not needed unless you plan to talk to SVN repos. # @@ -145,21 +287,51 @@ AC_CHECK_FUNC(setenv,[], # Define NO_OPENSSL environment variable if you do not have OpenSSL. # This also implies MOZILLA_SHA1. # +# Define OPENSSLDIR=/foo/bar if your openssl header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(openssl, +AS_HELP_STRING([--with-openssl],[use OpenSSL library (default is YES)]) +AS_HELP_STRING([], [ARG can be prefix for openssl library and headers]),\ +GIT_PARSE_WITH(openssl)) +# # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. # # Define CURLDIR=/foo/bar if your curl header and library files are in # /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(curl, +AS_HELP_STRING([--with-curl],[support http(s):// transports (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for curl library and headers]), +GIT_PARSE_WITH(curl)) # # Define NO_EXPAT if you do not have expat installed. git-http-push is # not built, and you cannot push using http:// and https:// transports. # -# Define NO_MMAP if you want to avoid mmap. +# Define EXPATDIR=/foo/bar if your expat header and library files are in +# /foo/bar/include and /foo/bar/lib directories. +AC_ARG_WITH(expat, +AS_HELP_STRING([--with-expat], +[support git-push using http:// and https:// transports via WebDAV (default is YES)]) +AS_HELP_STRING([], [ARG can be also prefix for expat library and headers]), +GIT_PARSE_WITH(expat)) +# +# Define NO_FINK if you are building on Darwin/Mac OS X, have Fink +# installed in /sw, but don't want GIT to link against any libraries +# installed there. If defined you may specify your own (or Fink's) +# include directories and library directories by defining CFLAGS +# and LDFLAGS appropriately. # -# Define NO_PYTHON if you want to loose all benefits of the recursive merge. +# Define NO_DARWIN_PORTS if you are building on Darwin/Mac OS X, +# have DarwinPorts installed in /opt/local, but don't want GIT to +# link against any libraries installed there. If defined you may +# specify your own (or DarwinPort's) include directories and +# library directories by defining CFLAGS and LDFLAGS appropriately. # +# Define NO_MMAP if you want to avoid mmap. + ## --enable-FEATURE[=ARG] and --disable-FEATURE +# # Define COLLISION_CHECK below if you believe that SHA1's # 1461501637330902918203684832716283019655932542976 hashes do not give you # sufficient guarantee that no collisions between objects will ever happen. @@ -10,7 +10,7 @@ #include <netdb.h> #include <signal.h> -static char *server_capabilities = NULL; +static char *server_capabilities; static int check_ref(const char *name, int len, unsigned int flags) { @@ -493,8 +493,8 @@ static void git_tcp_connect(int fd[2], char *host) } -static char *git_proxy_command = NULL; -static const char *rhost_name = NULL; +static char *git_proxy_command; +static const char *rhost_name; static int rhost_len; static int git_proxy_command_options(const char *var, const char *value) diff --git a/contrib/emacs/vc-git.el b/contrib/emacs/vc-git.el index 3f6ed699f0..4a8f79092d 100644 --- a/contrib/emacs/vc-git.el +++ b/contrib/emacs/vc-git.el @@ -54,7 +54,7 @@ (let* ((dir (file-name-directory file)) (name (file-relative-name file dir))) (when dir (cd dir)) - (and (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name)) + (and (ignore-errors (eq 0 (call-process "git" nil '(t nil) nil "ls-files" "-c" "-z" "--" name))) (let ((str (buffer-string))) (and (> (length str) (length name)) (string= (substring str 0 (1+ (length name))) (concat name "\0")))))))) diff --git a/convert-objects.c b/convert-objects.c index 168771ed85..4e7ff7517f 100644 --- a/convert-objects.c +++ b/convert-objects.c @@ -39,7 +39,7 @@ static struct entry *lookup_entry(unsigned char *sha1) while (low < high) { int next = (low + high) / 2; struct entry *n = convert[next]; - int cmp = memcmp(sha1, n->old_sha1, 20); + int cmp = hashcmp(sha1, n->old_sha1); if (!cmp) return n; if (cmp < 0) { diff --git a/csum-file.c b/csum-file.c index 6a7b40fd09..e2278897d0 100644 --- a/csum-file.c +++ b/csum-file.c @@ -10,7 +10,7 @@ #include "cache.h" #include "csum-file.h" -static int sha1flush(struct sha1file *f, unsigned int count) +static void sha1flush(struct sha1file *f, unsigned int count) { void *buf = f->buffer; @@ -21,7 +21,7 @@ static int sha1flush(struct sha1file *f, unsigned int count) count -= ret; if (count) continue; - return 0; + return; } if (!ret) die("sha1 file '%s' write error. Out of diskspace", f->name); @@ -22,24 +22,24 @@ static const char daemon_usage[] = " [--reuseaddr] [--detach] [--pid-file=file] [directory...]"; /* List of acceptable pathname prefixes */ -static char **ok_paths = NULL; -static int strict_paths = 0; +static char **ok_paths; +static int strict_paths; /* If this is set, git-daemon-export-ok is not required */ -static int export_all_trees = 0; +static int export_all_trees; /* Take all paths relative to this one if non-NULL */ -static char *base_path = NULL; +static char *base_path; /* If defined, ~user notation is allowed and the string is inserted * after ~user/. E.g. a request to git://host/~alice/frotz would * go to /home/alice/pub_git/frotz with --user-path=pub_git. */ -static const char *user_path = NULL; +static const char *user_path; /* Timeout, and initial timeout */ -static unsigned int timeout = 0; -static unsigned int init_timeout = 0; +static unsigned int timeout; +static unsigned int init_timeout; static void logreport(int priority, const char *err, va_list params) { @@ -333,12 +333,12 @@ static int execute(struct sockaddr *addr) static int max_connections = 25; /* These are updated by the signal handler */ -static volatile unsigned int children_reaped = 0; +static volatile unsigned int children_reaped; static pid_t dead_child[MAX_CHILDREN]; /* These are updated by the main loop */ -static unsigned int children_spawned = 0; -static unsigned int children_deleted = 0; +static unsigned int children_spawned; +static unsigned int children_deleted; static struct child { pid_t pid; diff --git a/describe.c b/describe.c index 324ca8965b..2b9301fc12 100644 --- a/describe.c +++ b/describe.c @@ -8,12 +8,12 @@ static const char describe_usage[] = "git-describe [--all] [--tags] [--abbrev=<n>] <committish>*"; -static int all = 0; /* Default to annotated tags only */ -static int tags = 0; /* But allow any tags if --tags is specified */ +static int all; /* Default to annotated tags only */ +static int tags; /* But allow any tags if --tags is specified */ static int abbrev = DEFAULT_ABBREV; -static int names = 0, allocs = 0; +static int names, allocs; static struct commit_name { const struct commit *commit; int prio; /* annotated tag = 2, tag = 1, head = 0 */ diff --git a/diff-lib.c b/diff-lib.c index 116b5a9d68..3e4b3b96b7 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -215,7 +215,7 @@ static int show_modified(struct rev_info *revs, } oldmode = old->ce_mode; - if (mode == oldmode && !memcmp(sha1, old->sha1, 20) && + if (mode == oldmode && !hashcmp(sha1, old->sha1) && !revs->diffopt.find_copies_harder) return 0; @@ -13,9 +13,9 @@ static int use_size_cache; -static int diff_detect_rename_default = 0; +static int diff_detect_rename_default; static int diff_rename_limit_default = -1; -static int diff_use_color_default = 0; +static int diff_use_color_default; /* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */ static char diff_colors[][24] = { @@ -358,12 +358,152 @@ static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one) return 0; } +struct diff_words_buffer { + mmfile_t text; + long alloc; + long current; /* output pointer */ + int suppressed_newline; +}; + +static void diff_words_append(char *line, unsigned long len, + struct diff_words_buffer *buffer) +{ + if (buffer->text.size + len > buffer->alloc) { + buffer->alloc = (buffer->text.size + len) * 3 / 2; + buffer->text.ptr = xrealloc(buffer->text.ptr, buffer->alloc); + } + line++; + len--; + memcpy(buffer->text.ptr + buffer->text.size, line, len); + buffer->text.size += len; +} + +struct diff_words_data { + struct xdiff_emit_state xm; + struct diff_words_buffer minus, plus; +}; + +static void print_word(struct diff_words_buffer *buffer, int len, int color, + int suppress_newline) +{ + const char *ptr; + int eol = 0; + + if (len == 0) + return; + + ptr = buffer->text.ptr + buffer->current; + buffer->current += len; + + if (ptr[len - 1] == '\n') { + eol = 1; + len--; + } + + fputs(diff_get_color(1, color), stdout); + fwrite(ptr, len, 1, stdout); + fputs(diff_get_color(1, DIFF_RESET), stdout); + + if (eol) { + if (suppress_newline) + buffer->suppressed_newline = 1; + else + putchar('\n'); + } +} + +static void fn_out_diff_words_aux(void *priv, char *line, unsigned long len) +{ + struct diff_words_data *diff_words = priv; + + if (diff_words->minus.suppressed_newline) { + if (line[0] != '+') + putchar('\n'); + diff_words->minus.suppressed_newline = 0; + } + + len--; + switch (line[0]) { + case '-': + print_word(&diff_words->minus, len, DIFF_FILE_OLD, 1); + break; + case '+': + print_word(&diff_words->plus, len, DIFF_FILE_NEW, 0); + break; + case ' ': + print_word(&diff_words->plus, len, DIFF_PLAIN, 0); + diff_words->minus.current += len; + break; + } +} + +/* this executes the word diff on the accumulated buffers */ +static void diff_words_show(struct diff_words_data *diff_words) +{ + xpparam_t xpp; + xdemitconf_t xecfg; + xdemitcb_t ecb; + mmfile_t minus, plus; + int i; + + minus.size = diff_words->minus.text.size; + minus.ptr = xmalloc(minus.size); + memcpy(minus.ptr, diff_words->minus.text.ptr, minus.size); + for (i = 0; i < minus.size; i++) + if (isspace(minus.ptr[i])) + minus.ptr[i] = '\n'; + diff_words->minus.current = 0; + + plus.size = diff_words->plus.text.size; + plus.ptr = xmalloc(plus.size); + memcpy(plus.ptr, diff_words->plus.text.ptr, plus.size); + for (i = 0; i < plus.size; i++) + if (isspace(plus.ptr[i])) + plus.ptr[i] = '\n'; + diff_words->plus.current = 0; + + xpp.flags = XDF_NEED_MINIMAL; + xecfg.ctxlen = diff_words->minus.alloc + diff_words->plus.alloc; + xecfg.flags = 0; + ecb.outf = xdiff_outf; + ecb.priv = diff_words; + diff_words->xm.consume = fn_out_diff_words_aux; + xdl_diff(&minus, &plus, &xpp, &xecfg, &ecb); + + free(minus.ptr); + free(plus.ptr); + diff_words->minus.text.size = diff_words->plus.text.size = 0; + + if (diff_words->minus.suppressed_newline) { + putchar('\n'); + diff_words->minus.suppressed_newline = 0; + } +} + struct emit_callback { struct xdiff_emit_state xm; int nparents, color_diff; const char **label_path; + struct diff_words_data *diff_words; }; +static void free_diff_words_data(struct emit_callback *ecbdata) +{ + if (ecbdata->diff_words) { + /* flush buffers */ + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + + if (ecbdata->diff_words->minus.text.ptr) + free (ecbdata->diff_words->minus.text.ptr); + if (ecbdata->diff_words->plus.text.ptr) + free (ecbdata->diff_words->plus.text.ptr); + free(ecbdata->diff_words); + ecbdata->diff_words = NULL; + } +} + const char *diff_get_color(int diff_use_color, enum color_diff ix) { if (diff_use_color) @@ -398,12 +538,31 @@ static void fn_out_consume(void *priv, char *line, unsigned long len) else { int nparents = ecbdata->nparents; int color = DIFF_PLAIN; - for (i = 0; i < nparents && len; i++) { - if (line[i] == '-') - color = DIFF_FILE_OLD; - else if (line[i] == '+') - color = DIFF_FILE_NEW; - } + if (ecbdata->diff_words && nparents != 1) + /* fall back to normal diff */ + free_diff_words_data(ecbdata); + if (ecbdata->diff_words) { + if (line[0] == '-') { + diff_words_append(line, len, + &ecbdata->diff_words->minus); + return; + } else if (line[0] == '+') { + diff_words_append(line, len, + &ecbdata->diff_words->plus); + return; + } + if (ecbdata->diff_words->minus.text.size || + ecbdata->diff_words->plus.text.size) + diff_words_show(ecbdata->diff_words); + line++; + len--; + } else + for (i = 0; i < nparents && len; i++) { + if (line[i] == '-') + color = DIFF_FILE_OLD; + else if (line[i] == '+') + color = DIFF_FILE_NEW; + } set = diff_get_color(ecbdata->color_diff, color); } if (len > 0 && line[len-1] == '\n') @@ -745,9 +904,7 @@ static int mmfile_is_binary(mmfile_t *mf) long sz = mf->size; if (FIRST_FEW_BYTES < sz) sz = FIRST_FEW_BYTES; - if (memchr(mf->ptr, 0, sz)) - return 1; - return 0; + return !!memchr(mf->ptr, 0, sz); } static void builtin_diff(const char *name_a, @@ -836,7 +993,12 @@ static void builtin_diff(const char *name_a, ecb.outf = xdiff_outf; ecb.priv = &ecbdata; ecbdata.xm.consume = fn_out_consume; + if (o->color_diff_words) + ecbdata.diff_words = + xcalloc(1, sizeof(struct diff_words_data)); xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb); + if (o->color_diff_words) + free_diff_words_data(&ecbdata); } free_ab_and_return: @@ -940,7 +1102,7 @@ void fill_filespec(struct diff_filespec *spec, const unsigned char *sha1, if (mode) { spec->mode = canon_mode(mode); memcpy(spec->sha1, sha1, 20); - spec->sha1_valid = !!memcmp(sha1, null_sha1, 20); + spec->sha1_valid = !is_null_sha1(sha1); } } @@ -978,7 +1140,7 @@ static int work_tree_matches(const char *name, const unsigned char *sha1) if ((lstat(name, &st) < 0) || !S_ISREG(st.st_mode) || /* careful! */ ce_match_stat(ce, &st, 0) || - memcmp(sha1, ce->sha1, 20)) + hashcmp(sha1, ce->sha1)) return 0; /* we return 1 only when we can stat, it is a regular file, * stat information matches, and sha1 recorded in the cache @@ -1006,7 +1168,7 @@ static struct sha1_size_cache *locate_size_cache(unsigned char *sha1, while (last > first) { int cmp, next = (last + first) >> 1; e = sha1_size_cache[next]; - cmp = memcmp(e->sha1, sha1, 20); + cmp = hashcmp(e->sha1, sha1); if (!cmp) return e; if (cmp < 0) { @@ -1417,7 +1579,7 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o) ; } - if (memcmp(one->sha1, two->sha1, 20)) { + if (hashcmp(one->sha1, two->sha1)) { int abbrev = o->full_index ? 40 : DEFAULT_ABBREV; len += snprintf(msg + len, sizeof(msg) - len, @@ -1515,6 +1677,19 @@ void diff_setup(struct diff_options *options) int diff_setup_done(struct diff_options *options) { + int count = 0; + + if (options->output_format & DIFF_FORMAT_NAME) + count++; + if (options->output_format & DIFF_FORMAT_NAME_STATUS) + count++; + if (options->output_format & DIFF_FORMAT_CHECKDIFF) + count++; + if (options->output_format & DIFF_FORMAT_NO_OUTPUT) + count++; + if (count > 1) + die("--name-only, --name-status, --check and -s are mutually exclusive"); + if (options->find_copies_harder) options->detect_rename = DIFF_DETECT_COPY; @@ -1697,6 +1872,8 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac) options->xdl_opts |= XDF_IGNORE_WHITESPACE; else if (!strcmp(arg, "-b") || !strcmp(arg, "--ignore-space-change")) options->xdl_opts |= XDF_IGNORE_WHITESPACE_CHANGE; + else if (!strcmp(arg, "--color-words")) + options->color_diff = options->color_diff_words = 1; else if (!strcmp(arg, "--no-renames")) options->detect_rename = 0; else @@ -1921,7 +2098,7 @@ int diff_unmodified_pair(struct diff_filepair *p) * dealing with a change. */ if (one->sha1_valid && two->sha1_valid && - !memcmp(one->sha1, two->sha1, sizeof(one->sha1))) + !hashcmp(one->sha1, two->sha1)) return 1; /* no change */ if (!one->sha1_valid && !two->sha1_valid) return 1; /* both look at the same file on the filesystem. */ @@ -2060,7 +2237,7 @@ static void diff_resolve_rename_copy(void) if (!p->status) p->status = DIFF_STATUS_RENAMED; } - else if (memcmp(p->one->sha1, p->two->sha1, 20) || + else if (hashcmp(p->one->sha1, p->two->sha1) || p->one->mode != p->two->mode) p->status = DIFF_STATUS_MODIFIED; else { @@ -46,7 +46,8 @@ struct diff_options { full_index:1, silent_on_remove:1, find_copies_harder:1, - color_diff:1; + color_diff:1, + color_diff_words:1; int context; int break_opt; int detect_rename; diff --git a/diffcore-break.c b/diffcore-break.c index ed0e14c6d8..acb18db1db 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -56,7 +56,7 @@ static int should_break(struct diff_filespec *src, return 0; /* leave symlink rename alone */ if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 0; /* they are the same */ if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0)) diff --git a/diffcore-rename.c b/diffcore-rename.c index 0ec488a903..ef239012b6 100644 --- a/diffcore-rename.c +++ b/diffcore-rename.c @@ -101,7 +101,7 @@ static int is_exact_match(struct diff_filespec *src, int contents_too) { if (src->sha1_valid && dst->sha1_valid && - !memcmp(src->sha1, dst->sha1, 20)) + !hashcmp(src->sha1, dst->sha1)) return 1; if (!contents_too) return 0; diff --git a/dump-cache-tree.c b/dump-cache-tree.c index 1ccaf51773..1f73f1ea7d 100644 --- a/dump-cache-tree.c +++ b/dump-cache-tree.c @@ -33,7 +33,7 @@ static int dump_cache_tree(struct cache_tree *it, } else { dump_one(it, pfx, ""); - if (memcmp(it->sha1, ref->sha1, 20) || + if (hashcmp(it->sha1, ref->sha1) || ref->entry_count != it->entry_count || ref->subtree_nr != it->subtree_nr) { dump_one(ref, pfx, "#(ref) "); diff --git a/environment.c b/environment.c index 87162b2572..e6bd0033b4 100644 --- a/environment.c +++ b/environment.c @@ -13,14 +13,14 @@ char git_default_email[MAX_GITNAME]; char git_default_name[MAX_GITNAME]; int use_legacy_headers = 1; int trust_executable_bit = 1; -int assume_unchanged = 0; -int prefer_symlink_refs = 0; -int log_all_ref_updates = 0; +int assume_unchanged; +int prefer_symlink_refs; +int log_all_ref_updates; int warn_ambiguous_refs = 1; -int repository_format_version = 0; +int repository_format_version; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = PERM_UMASK; -const char *apply_default_whitespace = NULL; +const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; int pager_in_use; int pager_use_color = 1; diff --git a/exec_cmd.c b/exec_cmd.c index 62f51fcd6e..e30936d72c 100644 --- a/exec_cmd.c +++ b/exec_cmd.c @@ -5,7 +5,7 @@ extern char **environ; static const char *builtin_exec_path = GIT_EXEC_PATH; -static const char *current_exec_path = NULL; +static const char *current_exec_path; void git_set_exec_path(const char *exec_path) { diff --git a/fetch-clone.c b/fetch-clone.c index 5e84c4620f..c5cf4776fa 100644 --- a/fetch-clone.c +++ b/fetch-clone.c @@ -44,9 +44,8 @@ static int finish_pack(const char *pack_tmp_name, const char *me) for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); - if (retval < 0) { + if (waitpid(pid, &status, 0) < 0) { if (errno == EINTR) continue; error("waitpid failed (%s)", strerror(errno)); diff --git a/fetch-pack.c b/fetch-pack.c index b7824dbed4..e18c1489a1 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -24,8 +24,8 @@ static const char *exec = "git-upload-pack"; */ #define MAX_IN_VAIN 256 -static struct commit_list *rev_list = NULL; -static int non_common_revs = 0, multi_ack = 0, use_thin_pack = 0, use_sideband; +static struct commit_list *rev_list; +static int non_common_revs, multi_ack, use_thin_pack, use_sideband; static void rev_list_push(struct commit *commit, int mark) { @@ -250,7 +250,7 @@ done: return retval; } -static struct commit_list *complete = NULL; +static struct commit_list *complete; static int mark_complete(const char *path, const unsigned char *sha1) { diff --git a/fsck-objects.c b/fsck-objects.c index e167f4105f..31e00d84b3 100644 --- a/fsck-objects.c +++ b/fsck-objects.c @@ -14,12 +14,12 @@ #define REACHABLE 0x0001 #define SEEN 0x0002 -static int show_root = 0; -static int show_tags = 0; -static int show_unreachable = 0; -static int check_full = 0; -static int check_strict = 0; -static int keep_cache_objects = 0; +static int show_root; +static int show_tags; +static int show_unreachable; +static int check_full; +static int check_strict; +static int keep_cache_objects; static unsigned char head_sha1[20]; #ifdef NO_D_INO_IN_DIRENT @@ -366,13 +366,13 @@ static void add_sha1_list(unsigned char *sha1, unsigned long ino) sha1_list.nr = ++nr; } -static int fsck_dir(int i, char *path) +static void fsck_dir(int i, char *path) { DIR *dir = opendir(path); struct dirent *de; if (!dir) - return 0; + return; while ((de = readdir(dir)) != NULL) { char name[100]; @@ -398,10 +398,9 @@ static int fsck_dir(int i, char *path) fprintf(stderr, "bad sha1 file: %s/%s\n", path, de->d_name); } closedir(dir); - return 0; } -static int default_refs = 0; +static int default_refs; static int fsck_handle_ref(const char *refname, const unsigned char *sha1) { @@ -453,7 +452,7 @@ static int fsck_head_link(void) if (strncmp(git_refs_heads_master + pfxlen, "refs/heads/", 11)) return error("HEAD points to something strange (%s)", git_refs_heads_master + pfxlen); - if (!memcmp(null_sha1, sha1, 20)) + if (is_null_sha1(sha1)) return error("HEAD: not a valid git pointer"); return 0; } @@ -92,7 +92,7 @@ static int handle_options(const char*** argv, int* argc) } static const char *alias_command; -static char *alias_string = NULL; +static char *alias_string; static int git_alias_config(const char *var, const char *value) { @@ -213,8 +213,8 @@ static int handle_alias(int *argcp, const char ***argv) const char git_version_string[] = GIT_VERSION; -#define NEEDS_PREFIX 1 -#define USE_PAGER 2 +#define RUN_SETUP (1<<0) +#define USE_PAGER (1<<1) static void handle_internal_command(int argc, const char **argv, char **envp) { @@ -224,47 +224,53 @@ static void handle_internal_command(int argc, const char **argv, char **envp) int (*fn)(int, const char **, const char *); int option; } commands[] = { - { "version", cmd_version }, - { "help", cmd_help }, - { "log", cmd_log, NEEDS_PREFIX | USE_PAGER }, - { "whatchanged", cmd_whatchanged, NEEDS_PREFIX | USE_PAGER }, - { "show", cmd_show, NEEDS_PREFIX | USE_PAGER }, - { "push", cmd_push, NEEDS_PREFIX }, - { "format-patch", cmd_format_patch, NEEDS_PREFIX }, + { "add", cmd_add, RUN_SETUP }, + { "apply", cmd_apply }, + { "cat-file", cmd_cat_file, RUN_SETUP }, + { "checkout-index", cmd_checkout_index, RUN_SETUP }, + { "check-ref-format", cmd_check_ref_format }, + { "commit-tree", cmd_commit_tree, RUN_SETUP }, { "count-objects", cmd_count_objects }, - { "diff", cmd_diff, NEEDS_PREFIX }, - { "grep", cmd_grep, NEEDS_PREFIX }, - { "rm", cmd_rm, NEEDS_PREFIX }, - { "add", cmd_add, NEEDS_PREFIX }, - { "rev-list", cmd_rev_list, NEEDS_PREFIX }, - { "init-db", cmd_init_db }, + { "diff", cmd_diff, RUN_SETUP }, + { "diff-files", cmd_diff_files, RUN_SETUP }, + { "diff-index", cmd_diff_index, RUN_SETUP }, + { "diff-stages", cmd_diff_stages, RUN_SETUP }, + { "diff-tree", cmd_diff_tree, RUN_SETUP }, + { "fmt-merge-msg", cmd_fmt_merge_msg, RUN_SETUP }, + { "format-patch", cmd_format_patch, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id }, - { "upload-tar", cmd_upload_tar }, - { "check-ref-format", cmd_check_ref_format }, - { "ls-files", cmd_ls_files, NEEDS_PREFIX }, - { "ls-tree", cmd_ls_tree, NEEDS_PREFIX }, - { "tar-tree", cmd_tar_tree, NEEDS_PREFIX }, - { "read-tree", cmd_read_tree, NEEDS_PREFIX }, - { "commit-tree", cmd_commit_tree, NEEDS_PREFIX }, - { "apply", cmd_apply }, - { "show-branch", cmd_show_branch, NEEDS_PREFIX }, - { "diff-files", cmd_diff_files, NEEDS_PREFIX }, - { "diff-index", cmd_diff_index, NEEDS_PREFIX }, - { "diff-stages", cmd_diff_stages, NEEDS_PREFIX }, - { "diff-tree", cmd_diff_tree, NEEDS_PREFIX }, - { "cat-file", cmd_cat_file, NEEDS_PREFIX }, - { "rev-parse", cmd_rev_parse, NEEDS_PREFIX }, - { "write-tree", cmd_write_tree, NEEDS_PREFIX }, - { "mailsplit", cmd_mailsplit }, + { "grep", cmd_grep, RUN_SETUP }, + { "help", cmd_help }, + { "init-db", cmd_init_db }, + { "log", cmd_log, RUN_SETUP | USE_PAGER }, + { "ls-files", cmd_ls_files, RUN_SETUP }, + { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, - { "stripspace", cmd_stripspace }, - { "update-index", cmd_update_index, NEEDS_PREFIX }, - { "update-ref", cmd_update_ref, NEEDS_PREFIX }, - { "fmt-merge-msg", cmd_fmt_merge_msg, NEEDS_PREFIX }, - { "prune", cmd_prune, NEEDS_PREFIX }, - { "mv", cmd_mv, NEEDS_PREFIX }, - { "prune-packed", cmd_prune_packed, NEEDS_PREFIX }, + { "mailsplit", cmd_mailsplit }, + { "mv", cmd_mv, RUN_SETUP }, + { "name-rev", cmd_name_rev, RUN_SETUP }, + { "pack-objects", cmd_pack_objects, RUN_SETUP }, + { "prune", cmd_prune, RUN_SETUP }, + { "prune-packed", cmd_prune_packed, RUN_SETUP }, + { "push", cmd_push, RUN_SETUP }, + { "read-tree", cmd_read_tree, RUN_SETUP }, { "repo-config", cmd_repo_config }, + { "rev-list", cmd_rev_list, RUN_SETUP }, + { "rev-parse", cmd_rev_parse, RUN_SETUP }, + { "rm", cmd_rm, RUN_SETUP }, + { "show-branch", cmd_show_branch, RUN_SETUP }, + { "show", cmd_show, RUN_SETUP | USE_PAGER }, + { "stripspace", cmd_stripspace }, + { "symbolic-ref", cmd_symbolic_ref, RUN_SETUP }, + { "tar-tree", cmd_tar_tree, RUN_SETUP }, + { "unpack-objects", cmd_unpack_objects, RUN_SETUP }, + { "update-index", cmd_update_index, RUN_SETUP }, + { "update-ref", cmd_update_ref, RUN_SETUP }, + { "upload-tar", cmd_upload_tar }, + { "version", cmd_version }, + { "whatchanged", cmd_whatchanged, RUN_SETUP | USE_PAGER }, + { "write-tree", cmd_write_tree, RUN_SETUP }, + { "verify-pack", cmd_verify_pack }, }; int i; @@ -281,7 +287,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) continue; prefix = NULL; - if (p->option & NEEDS_PREFIX) + if (p->option & RUN_SETUP) prefix = setup_git_directory(); if (p->option & USE_PAGER) setup_pager(); diff --git a/gitweb/README b/gitweb/README index 8d672762ea..27c6dac143 100644 --- a/gitweb/README +++ b/gitweb/README @@ -5,5 +5,33 @@ The one working on: From the git version 1.4.0 gitweb is bundled with git. -Any comment/question/concern to: + +How to configure gitweb for your local system: + +You can specify the following configuration variables when building GIT: + * GITWEB_SITENAME + Shown in the title of all generated pages, defaults to the servers name. + * GITWEB_PROJECTROOT + The root directory for all projects shown by gitweb. + * GITWEB_LIST + points to a directory to scan for projects (defaults to project root) + or to a file for explicit listing of projects. + * GITWEB_HOMETEXT + points to an .html file which is included on the gitweb project + overview page. + * GITWEB_CSS + Points to the location where you put gitweb.css on your web server. + * GITWEB_LOGO + Points to the location where you put git-logo.png on your web server. + * GITWEB_CONFIG + This file will be loaded using 'require'. If the environment + $GITWEB_CONFIG is set when gitweb.cgi is executed the file in the + environment variable will be loaded instead of the file + specified when gitweb.cgi was created. + +Originally written by: Kay Sievers <kay.sievers@vrfy.org> + +Any comment/question/concern to: + Git mailing list <git@vger.kernel.org> + diff --git a/gitweb/git-logo.png b/gitweb/git-logo.png Binary files differnew file mode 100644 index 0000000000..16ae8d5382 --- /dev/null +++ b/gitweb/git-logo.png diff --git a/gitweb/gitweb.css b/gitweb/gitweb.css index fffdb13d09..9013895857 100644 --- a/gitweb/gitweb.css +++ b/gitweb/gitweb.css @@ -117,9 +117,14 @@ div.list_head { a.list { text-decoration: none; + font-weight: bold; color: #000000; } +table.tags a.list { + font-weight: normal; +} + a.list:hover { text-decoration: underline; color: #880000; @@ -171,6 +176,10 @@ tr.dark { background-color: #f6f6f0; } +tr.dark2 { + background-color: #f6f6f0; +} + tr.dark:hover { background-color: #edece6; } @@ -181,12 +190,16 @@ td { vertical-align: top; } -td.link { +td.link, td.selflink { padding: 2px 5px; font-family: sans-serif; font-size: 10px; } +td.selflink { + padding-right: 0px; +} + td.sha1 { font-family: monospace; } @@ -196,6 +209,10 @@ td.error { background-color: yellow; } +td.current_head { + text-decoration: underline; +} + table.diff_tree span.file_status.new { color: #008000; } @@ -209,6 +226,10 @@ table.diff_tree span.file_status.mode_chnge { color: #777777; } +table.diff_tree span.file_status.copied { + color: #70a070; +} + /* age2: 60*60*24*2 <= age */ table.project_list td.age2, table.blame td.age2 { font-style: italic; @@ -309,15 +330,30 @@ a.rss_logo:hover { background-color: #ee5500; } -span.tag { +span.refs span { padding: 0px 4px; font-size: 10px; font-weight: normal; - background-color: #ffffaa; border: 1px solid; + background-color: #ffaaff; + border-color: #ffccff #ff00ee #ff00ee #ffccff; +} + +span.refs span.ref { + background-color: #aaaaff; + border-color: #ccccff #0033cc #0033cc #ccccff; +} + +span.refs span.tag { + background-color: #ffffaa; border-color: #ffffcc #ffee00 #ffee00 #ffffcc; } +span.refs span.head { + background-color: #aaffaa; + border-color: #ccffcc #00cc33 #00cc33 #ccffcc; +} + span.atnight { color: #cc0000; } diff --git a/gitweb/gitweb.cgi b/gitweb/gitweb.perl index e5fca63b9c..04282fa5e3 100755 --- a/gitweb/gitweb.cgi +++ b/gitweb/gitweb.perl @@ -14,47 +14,49 @@ use CGI::Util qw(unescape); use CGI::Carp qw(fatalsToBrowser); use Encode; use Fcntl ':mode'; +use File::Find qw(); binmode STDOUT, ':utf8'; our $cgi = new CGI; -our $version = "267"; +our $version = "++GIT_VERSION++"; our $my_url = $cgi->url(); our $my_uri = $cgi->url(-absolute => 1); -our $rss_link = ""; # core git executable to use # this can just be "git" if your webserver has a sensible PATH -our $GIT = "/usr/bin/git"; +our $GIT = "++GIT_BINDIR++/git"; # absolute fs-path which will be prepended to the project path #our $projectroot = "/pub/scm"; -our $projectroot = "/home/kay/public_html/pub/scm"; - -# version of the core git binary -our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +our $projectroot = "++GITWEB_PROJECTROOT++"; # location for temporary files needed for diffs our $git_temp = "/tmp/gitweb"; -if (! -d $git_temp) { - mkdir($git_temp, 0700) || die_error("Couldn't mkdir $git_temp"); -} # target of the home link on top of all pages our $home_link = $my_uri; +# string of the home link on top of all pages +our $home_link_str = "++GITWEB_HOME_LINK_STR++"; + # name of your site or organization to appear in page titles # replace this with something more descriptive for clearer bookmarks -our $site_name = $ENV{'SERVER_NAME'} || "Untitled"; +our $site_name = "++GITWEB_SITENAME++" || $ENV{'SERVER_NAME'} || "Untitled"; # html text to include at home page -our $home_text = "indextext.html"; +our $home_text = "++GITWEB_HOMETEXT++"; # URI of default stylesheet -our $stylesheet = "gitweb.css"; +our $stylesheet = "++GITWEB_CSS++"; +# URI of GIT logo +our $logo = "++GITWEB_LOGO++"; # source of projects list -#our $projects_list = $projectroot; -our $projects_list = "index/index.aux"; +our $projects_list = "++GITWEB_LIST++"; + +# list of git base URLs used for URL to where fetch project from, +# i.e. full URL is "$git_base_url/$project" +our @git_base_url_list = ("++GITWEB_BASE_URL++"); # default blob_plain mimetype and default charset for text/plain blob our $default_blob_plain_mimetype = 'text/plain'; @@ -64,47 +66,46 @@ our $default_text_plain_charset = undef; # (relative to the current git repository) our $mimetypes_file = undef; +our $GITWEB_CONFIG = $ENV{'GITWEB_CONFIG'} || "++GITWEB_CONFIG++"; +require $GITWEB_CONFIG if -e $GITWEB_CONFIG; + +# version of the core git binary +our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; + +$projects_list ||= $projectroot; +if (! -d $git_temp) { + mkdir($git_temp, 0700) || die_error(undef, "Couldn't mkdir $git_temp"); +} + +# ====================================================================== # input validation and dispatch our $action = $cgi->param('a'); if (defined $action) { if ($action =~ m/[^0-9a-zA-Z\.\-_]/) { - undef $action; - die_error(undef, "Invalid action parameter."); + die_error(undef, "Invalid action parameter"); } - if ($action eq "git-logo.png") { - git_logo(); - exit; - } elsif ($action eq "opml") { + # action which does not check rest of parameters + if ($action eq "opml") { git_opml(); exit; } } -our $order = $cgi->param('o'); -if (defined $order) { - if ($order =~ m/[^0-9a-zA-Z_]/) { - undef $order; - die_error(undef, "Invalid order parameter."); - } -} - our $project = ($cgi->param('p') || $ENV{'PATH_INFO'}); if (defined $project) { - $project =~ s|^/||; $project =~ s|/$||; - $project = validate_input($project); - if (!defined($project)) { - die_error(undef, "Invalid project parameter."); + $project =~ s|^/||; + $project =~ s|/$||; +} +if (defined $project && $project) { + if (!validate_input($project)) { + die_error(undef, "Invalid project parameter"); } if (!(-d "$projectroot/$project")) { - undef $project; - die_error(undef, "No such directory."); + die_error(undef, "No such directory"); } if (!(-e "$projectroot/$project/HEAD")) { - undef $project; - die_error(undef, "No such project."); + die_error(undef, "No such project"); } - $rss_link = "<link rel=\"alternate\" title=\"" . esc_param($project) . " log\" href=\"" . - "$my_uri?" . esc_param("p=$project;a=rss") . "\" type=\"application/rss+xml\"/>"; $ENV{'GIT_DIR'} = "$projectroot/$project"; } else { git_project_list(); @@ -113,53 +114,106 @@ if (defined $project) { our $file_name = $cgi->param('f'); if (defined $file_name) { - $file_name = validate_input($file_name); - if (!defined($file_name)) { - die_error(undef, "Invalid file parameter."); + if (!validate_input($file_name)) { + die_error(undef, "Invalid file parameter"); } } our $hash = $cgi->param('h'); if (defined $hash) { - $hash = validate_input($hash); - if (!defined($hash)) { - die_error(undef, "Invalid hash parameter."); + if (!validate_input($hash)) { + die_error(undef, "Invalid hash parameter"); } } our $hash_parent = $cgi->param('hp'); if (defined $hash_parent) { - $hash_parent = validate_input($hash_parent); - if (!defined($hash_parent)) { - die_error(undef, "Invalid hash parent parameter."); + if (!validate_input($hash_parent)) { + die_error(undef, "Invalid hash parent parameter"); } } our $hash_base = $cgi->param('hb'); if (defined $hash_base) { - $hash_base = validate_input($hash_base); - if (!defined($hash_base)) { - die_error(undef, "Invalid hash base parameter."); + if (!validate_input($hash_base)) { + die_error(undef, "Invalid hash base parameter"); } } our $page = $cgi->param('pg'); if (defined $page) { if ($page =~ m/[^0-9]$/) { - undef $page; - die_error(undef, "Invalid page parameter."); + die_error(undef, "Invalid page parameter"); } } our $searchtext = $cgi->param('s'); if (defined $searchtext) { if ($searchtext =~ m/[^a-zA-Z0-9_\.\/\-\+\:\@ ]/) { - undef $searchtext; - die_error(undef, "Invalid search parameter."); + die_error(undef, "Invalid search parameter"); } $searchtext = quotemeta $searchtext; } +# dispatch +my %actions = ( + "blame" => \&git_blame2, + "blobdiff" => \&git_blobdiff, + "blobdiff_plain" => \&git_blobdiff_plain, + "blob" => \&git_blob, + "blob_plain" => \&git_blob_plain, + "commitdiff" => \&git_commitdiff, + "commitdiff_plain" => \&git_commitdiff_plain, + "commit" => \&git_commit, + "heads" => \&git_heads, + "history" => \&git_history, + "log" => \&git_log, + "rss" => \&git_rss, + "search" => \&git_search, + "shortlog" => \&git_shortlog, + "summary" => \&git_summary, + "tag" => \&git_tag, + "tags" => \&git_tags, + "tree" => \&git_tree, +); + +$action = 'summary' if (!defined($action)); +if (!defined($actions{$action})) { + die_error(undef, "Unknown action"); +} +$actions{$action}->(); +exit; + +## ====================================================================== +## action links + +sub href(%) { + my %mapping = ( + action => "a", + project => "p", + file_name => "f", + hash => "h", + hash_parent => "hp", + hash_base => "hb", + page => "pg", + searchtext => "s", + ); + + my %params = @_; + $params{"project"} ||= $project; + + my $href = "$my_uri?"; + $href .= esc_param( join(";", + map { "$mapping{$_}=$params{$_}" } keys %params + ) ); + + return $href; +} + + +## ====================================================================== +## validation, quoting/unquoting and escaping + sub validate_input { my $input = shift; @@ -175,66 +229,6 @@ sub validate_input { return $input; } -if (!defined $action || $action eq "summary") { - git_summary(); - exit; -} elsif ($action eq "heads") { - git_heads(); - exit; -} elsif ($action eq "tags") { - git_tags(); - exit; -} elsif ($action eq "blob") { - git_blob(); - exit; -} elsif ($action eq "blob_plain") { - git_blob_plain(); - exit; -} elsif ($action eq "tree") { - git_tree(); - exit; -} elsif ($action eq "rss") { - git_rss(); - exit; -} elsif ($action eq "commit") { - git_commit(); - exit; -} elsif ($action eq "log") { - git_log(); - exit; -} elsif ($action eq "blobdiff") { - git_blobdiff(); - exit; -} elsif ($action eq "blobdiff_plain") { - git_blobdiff_plain(); - exit; -} elsif ($action eq "commitdiff") { - git_commitdiff(); - exit; -} elsif ($action eq "commitdiff_plain") { - git_commitdiff_plain(); - exit; -} elsif ($action eq "history") { - git_history(); - exit; -} elsif ($action eq "search") { - git_search(); - exit; -} elsif ($action eq "shortlog") { - git_shortlog(); - exit; -} elsif ($action eq "tag") { - git_tag(); - exit; -} elsif ($action eq "blame") { - git_blame2(); - exit; -} else { - undef $action; - die_error(undef, "Unknown action."); - exit; -} - # quote unsafe chars, but keep the slash, even when it's not # correct, but quoted slashes look too horrible in bookmarks sub esc_param { @@ -250,6 +244,7 @@ sub esc_html { my $str = shift; $str = decode("utf8", $str, Encode::FB_DEFAULT); $str = escapeHTML($str); + $str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file) return $str; } @@ -263,6 +258,43 @@ sub unquote { return $str; } +# escape tabs (convert tabs to spaces) +sub untabify { + my $line = shift; + + while ((my $pos = index($line, "\t")) != -1) { + if (my $count = (8 - ($pos % 8))) { + my $spaces = ' ' x $count; + $line =~ s/\t/$spaces/; + } + } + + return $line; +} + +## ---------------------------------------------------------------------- +## HTML aware string manipulation + +sub chop_str { + my $str = shift; + my $len = shift; + my $add_len = shift || 10; + + # allow only $len chars, but don't cut a word if it would fit in $add_len + # if it doesn't fit, cut it if it's still longer than the dots we would add + $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; + my $body = $1; + my $tail = $2; + if (length($tail) > 4) { + $tail = " ..."; + $body =~ s/&[^;]*$//; # remove chopped character entities + } + return "$body$tail"; +} + +## ---------------------------------------------------------------------- +## functions returning short strings + # CSS class for given age value (in seconds) sub age_class { my $age = shift; @@ -276,126 +308,141 @@ sub age_class { } } -sub git_header_html { - my $status = shift || "200 OK"; - my $expires = shift; +# convert age in seconds to "nn units ago" string +sub age_string { + my $age = shift; + my $age_str; - my $title = "$site_name git"; - if (defined $project) { - $title .= " - $project"; - if (defined $action) { - $title .= "/$action"; - if (defined $file_name) { - $title .= " - $file_name"; - if ($action eq "tree" && $file_name !~ m|/$|) { - $title .= "/"; - } - } - } - } - my $content_type; - # require explicit support from the UA if we are to send the page as - # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. - # we have to do this because MSIE sometimes globs '*/*', pretending to - # support xhtml+xml but choking when it gets what it asked for. - if ($cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { - $content_type = 'application/xhtml+xml'; + if ($age > 60*60*24*365*2) { + $age_str = (int $age/60/60/24/365); + $age_str .= " years ago"; + } elsif ($age > 60*60*24*(365/12)*2) { + $age_str = int $age/60/60/24/(365/12); + $age_str .= " months ago"; + } elsif ($age > 60*60*24*7*2) { + $age_str = int $age/60/60/24/7; + $age_str .= " weeks ago"; + } elsif ($age > 60*60*24*2) { + $age_str = int $age/60/60/24; + $age_str .= " days ago"; + } elsif ($age > 60*60*2) { + $age_str = int $age/60/60; + $age_str .= " hours ago"; + } elsif ($age > 60*2) { + $age_str = int $age/60; + $age_str .= " min ago"; + } elsif ($age > 2) { + $age_str = int $age; + $age_str .= " sec ago"; } else { - $content_type = 'text/html'; + $age_str .= " right now"; } - print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); - print <<EOF; -<?xml version="1.0" encoding="utf-8"?> -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> -<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> -<!-- git web interface v$version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> -<!-- git core binaries version $git_version --> -<head> -<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> -<meta name="robots" content="index, nofollow"/> -<title>$title</title> -<link rel="stylesheet" type="text/css" href="$stylesheet"/> -$rss_link -</head> -<body> -EOF - print "<div class=\"page_header\">\n" . - "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . - "<img src=\"$my_uri?" . esc_param("a=git-logo.png") . "\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . - "</a>\n"; - print $cgi->a({-href => esc_param($home_link)}, "projects") . " / "; - if (defined $project) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, esc_html($project)); - if (defined $action) { - print " / $action"; - } - print "\n"; - if (!defined $searchtext) { - $searchtext = ""; - } - my $search_hash; - if (defined $hash_base) { - $search_hash = $hash_base; - } elsif (defined $hash) { - $search_hash = $hash; + return $age_str; +} + +# convert file mode in octal to symbolic file mode string +sub mode_str { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return 'drwxr-xr-x'; + } elsif (S_ISLNK($mode)) { + return 'lrwxrwxrwx'; + } elsif (S_ISREG($mode)) { + # git cares only about the executable bit + if ($mode & S_IXUSR) { + return '-rwxr-xr-x'; } else { - $search_hash = "HEAD"; - } - $cgi->param("a", "search"); - $cgi->param("h", $search_hash); - print $cgi->startform(-method => "get", -action => $my_uri) . - "<div class=\"search\">\n" . - $cgi->hidden(-name => "p") . "\n" . - $cgi->hidden(-name => "a") . "\n" . - $cgi->hidden(-name => "h") . "\n" . - $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . - "</div>" . - $cgi->end_form() . "\n"; + return '-rw-r--r--'; + }; + } else { + return '----------'; } - print "</div>\n"; } -sub git_footer_html { - print "<div class=\"page_footer\">\n"; - if (defined $project) { - my $descr = git_read_description($project); - if (defined $descr) { - print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=rss"), -class => "rss_logo"}, "RSS") . "\n"; +# convert file mode in octal to file type string +sub file_type { + my $mode = oct shift; + + if (S_ISDIR($mode & S_IFMT)) { + return "directory"; + } elsif (S_ISLNK($mode)) { + return "symlink"; + } elsif (S_ISREG($mode)) { + return "file"; } else { - print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + return "unknown"; } - print "</div>\n" . - "</body>\n" . - "</html>"; } -sub die_error { - my $status = shift || "403 Forbidden"; - my $error = shift || "Malformed query, file missing or permission denied"; +## ---------------------------------------------------------------------- +## functions returning short HTML fragments, or transforming HTML fragments +## which don't beling to other sections - git_header_html($status); - print "<div class=\"page_body\">\n" . - "<br/><br/>\n" . - "$status - $error\n" . - "<br/>\n" . - "</div>\n"; - git_footer_html(); - exit; +# format line of commit message or tag comment +sub format_log_line_html { + my $line = shift; + + $line = esc_html($line); + $line =~ s/ / /g; + if ($line =~ m/([0-9a-fA-F]{40})/) { + my $hash_text = $1; + if (git_get_type($hash_text) eq "commit") { + my $link = $cgi->a({-class => "text", -href => href(action=>"commit", hash=>$hash_text)}, $hash_text); + $line =~ s/$hash_text/$link/; + } + } + return $line; } -sub git_get_type { - my $hash = shift; +# format marker of refs pointing to given object +sub format_ref_marker { + my ($refs, $id) = @_; + my $markers = ''; - open my $fd, "-|", "$GIT cat-file -t $hash" or return; - my $type = <$fd>; - close $fd or return; - chomp $type; - return $type; + if (defined $refs->{$id}) { + foreach my $ref (@{$refs->{$id}}) { + my ($type, $name) = qw(); + # e.g. tags/v2.6.11 or heads/next + if ($ref =~ m!^(.*?)s?/(.*)$!) { + $type = $1; + $name = $2; + } else { + $type = "ref"; + $name = $ref; + } + + $markers .= " <span class=\"$type\">" . esc_html($name) . "</span>"; + } + } + + if ($markers) { + return ' <span class="refs">'. $markers . '</span>'; + } else { + return ""; + } } -sub git_read_head { +# format, perhaps shortened and with markers, title line +sub format_subject_html { + my ($long, $short, $href, $extra) = @_; + $extra = '' unless defined($extra); + + if (length($short) < length($long)) { + return $cgi->a({-href => $href, -class => "list", + -title => $long}, + esc_html($short) . $extra); + } else { + return $cgi->a({-href => $href, -class => "list"}, + esc_html($long) . $extra); + } +} + +## ---------------------------------------------------------------------- +## git utility subroutines, invoking git commands + +# get HEAD ref of given project as hash +sub git_get_head_hash { my $project = shift; my $oENV = $ENV{'GIT_DIR'}; my $retval = undef; @@ -413,7 +460,58 @@ sub git_read_head { return $retval; } -sub git_read_hash { +# get type of given object +sub git_get_type { + my $hash = shift; + + open my $fd, "-|", $GIT, "cat-file", '-t', $hash or return; + my $type = <$fd>; + close $fd or return; + chomp $type; + return $type; +} + +sub git_get_project_config { + my $key = shift; + + return unless ($key); + $key =~ s/^gitweb\.//; + return if ($key =~ m/\W/); + + my $val = qx($GIT repo-config --get gitweb.$key); + return ($val); +} + +sub git_get_project_config_bool { + my $val = git_get_project_config (@_); + if ($val and $val =~ m/true|yes|on/) { + return (1); + } + return; # implicit false +} + +# get hash of given path at given ref +sub git_get_hash_by_path { + my $base = shift; + my $path = shift || return undef; + + my $tree = $base; + + open my $fd, "-|", $GIT, "ls-tree", $base, "--", $path + or die_error(undef, "Open git-ls-tree failed"); + my $line = <$fd>; + close $fd or return undef; + + #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' + $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; + return $3; +} + +## ...................................................................... +## git utility functions, directly accessing git repository + +# assumes that PATH is not symref +sub git_get_hash_by_ref { my $path = shift; open my $fd, "$projectroot/$path" or return undef; @@ -425,7 +523,7 @@ sub git_read_hash { } } -sub git_read_description { +sub git_get_project_description { my $path = shift; open my $fd, "$projectroot/$path/description" or return undef; @@ -435,12 +533,153 @@ sub git_read_description { return $descr; } -sub git_read_tag { +sub git_get_project_url_list { + my $path = shift; + + open my $fd, "$projectroot/$path/cloneurl" or return undef; + my @git_project_url_list = map { chomp; $_ } <$fd>; + close $fd; + + return wantarray ? @git_project_url_list : \@git_project_url_list; +} + +sub git_get_projects_list { + my @list; + + if (-d $projects_list) { + # search in directory + my $dir = $projects_list; + opendir my ($dh), $dir or return undef; + while (my $dir = readdir($dh)) { + if (-e "$projectroot/$dir/HEAD") { + my $pr = { + path => $dir, + }; + push @list, $pr + } + } + closedir($dh); + } elsif (-f $projects_list) { + # read from file(url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + open my ($fd), $projects_list or return undef; + while (my $line = <$fd>) { + chomp $line; + my ($path, $owner) = split ' ', $line; + $path = unescape($path); + $owner = unescape($owner); + if (!defined $path) { + next; + } + if (-e "$projectroot/$path/HEAD") { + my $pr = { + path => $path, + owner => decode("utf8", $owner, Encode::FB_DEFAULT), + }; + push @list, $pr + } + } + close $fd; + } + @list = sort {$a->{'path'} cmp $b->{'path'}} @list; + return @list; +} + +sub git_get_project_owner { + my $project = shift; + my $owner; + + return undef unless $project; + + # read from file (url-encoded): + # 'git%2Fgit.git Linus+Torvalds' + # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' + # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' + if (-f $projects_list) { + open (my $fd , $projects_list); + while (my $line = <$fd>) { + chomp $line; + my ($pr, $ow) = split ' ', $line; + $pr = unescape($pr); + $ow = unescape($ow); + if ($pr eq $project) { + $owner = decode("utf8", $ow, Encode::FB_DEFAULT); + last; + } + } + close $fd; + } + if (!defined $owner) { + $owner = get_file_owner("$projectroot/$project"); + } + + return $owner; +} + +sub git_get_references { + my $type = shift || ""; + my %refs; + my $fd; + # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 + # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} + if (-f "$projectroot/$project/info/refs") { + open $fd, "$projectroot/$project/info/refs" + or return; + } else { + open $fd, "-|", $GIT, "ls-remote", "." + or return; + } + + while (my $line = <$fd>) { + chomp $line; + if ($line =~ m/^([0-9a-fA-F]{40})\trefs\/($type\/?[^\^]+)/) { + if (defined $refs{$1}) { + push @{$refs{$1}}, $2; + } else { + $refs{$1} = [ $2 ]; + } + } + } + close $fd or return; + return \%refs; +} + +## ---------------------------------------------------------------------- +## parse to hash functions + +sub parse_date { + my $epoch = shift; + my $tz = shift || "-0000"; + + my %date; + my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); + my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); + my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); + $date{'hour'} = $hour; + $date{'minute'} = $min; + $date{'mday'} = $mday; + $date{'day'} = $days[$wday]; + $date{'month'} = $months[$mon]; + $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; + $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; + + $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; + my $local = $epoch + ((int $1 + ($2/60)) * 3600); + ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); + $date{'hour_local'} = $hour; + $date{'minute_local'} = $min; + $date{'tz_local'} = $tz; + return %date; +} + +sub parse_tag { my $tag_id = shift; my %tag; my @comment; - open my $fd, "-|", "$GIT cat-file tag $tag_id" or return; + open my $fd, "-|", $GIT, "cat-file", "tag", $tag_id or return; $tag{'id'} = $tag_id; while (my $line = <$fd>) { chomp $line; @@ -470,38 +709,7 @@ sub git_read_tag { return %tag } -sub age_string { - my $age = shift; - my $age_str; - - if ($age > 60*60*24*365*2) { - $age_str = (int $age/60/60/24/365); - $age_str .= " years ago"; - } elsif ($age > 60*60*24*(365/12)*2) { - $age_str = int $age/60/60/24/(365/12); - $age_str .= " months ago"; - } elsif ($age > 60*60*24*7*2) { - $age_str = int $age/60/60/24/7; - $age_str .= " weeks ago"; - } elsif ($age > 60*60*24*2) { - $age_str = int $age/60/60/24; - $age_str .= " days ago"; - } elsif ($age > 60*60*2) { - $age_str = int $age/60/60; - $age_str .= " hours ago"; - } elsif ($age > 60*2) { - $age_str = int $age/60; - $age_str .= " min ago"; - } elsif ($age > 2) { - $age_str = int $age; - $age_str .= " sec ago"; - } else { - $age_str .= " right now"; - } - return $age_str; -} - -sub git_read_commit { +sub parse_commit { my $commit_id = shift; my $commit_text = shift; @@ -512,7 +720,7 @@ sub git_read_commit { @commit_lines = @$commit_text; } else { $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents --max-count=1 $commit_id" or return; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", "--max-count=1", $commit_id or return; @commit_lines = split '\n', <$fd>; close $fd or return; $/ = "\n"; @@ -595,6 +803,716 @@ sub git_read_commit { return %co; } +# parse ref from ref_file, given by ref_id, with given type +sub parse_ref { + my $ref_file = shift; + my $ref_id = shift; + my $type = shift || git_get_type($ref_id); + my %ref_item; + + $ref_item{'type'} = $type; + $ref_item{'id'} = $ref_id; + $ref_item{'epoch'} = 0; + $ref_item{'age'} = "unknown"; + if ($type eq "tag") { + my %tag = parse_tag($ref_id); + $ref_item{'comment'} = $tag{'comment'}; + if ($tag{'type'} eq "commit") { + my %co = parse_commit($tag{'object'}); + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } elsif (defined($tag{'epoch'})) { + my $age = time - $tag{'epoch'}; + $ref_item{'epoch'} = $tag{'epoch'}; + $ref_item{'age'} = age_string($age); + } + $ref_item{'reftype'} = $tag{'type'}; + $ref_item{'name'} = $tag{'name'}; + $ref_item{'refid'} = $tag{'object'}; + } elsif ($type eq "commit"){ + my %co = parse_commit($ref_id); + $ref_item{'reftype'} = "commit"; + $ref_item{'name'} = $ref_file; + $ref_item{'title'} = $co{'title'}; + $ref_item{'refid'} = $ref_id; + $ref_item{'epoch'} = $co{'committer_epoch'}; + $ref_item{'age'} = $co{'age_string'}; + } else { + $ref_item{'reftype'} = $type; + $ref_item{'name'} = $ref_file; + $ref_item{'refid'} = $ref_id; + } + + return %ref_item; +} + +## ...................................................................... +## parse to array of hashes functions + +sub git_get_refs_list { + my $ref_dir = shift; + my @reflist; + + my @refs; + my $pfxlen = length("$projectroot/$project/$ref_dir"); + File::Find::find(sub { + return if (/^\./); + if (-f $_) { + push @refs, substr($File::Find::name, $pfxlen + 1); + } + }, "$projectroot/$project/$ref_dir"); + + foreach my $ref_file (@refs) { + my $ref_id = git_get_hash_by_ref("$project/$ref_dir/$ref_file"); + my $type = git_get_type($ref_id) || next; + my %ref_item = parse_ref($ref_file, $ref_id, $type); + + push @reflist, \%ref_item; + } + # sort refs by age + @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; + return \@reflist; +} + +## ---------------------------------------------------------------------- +## filesystem-related functions + +sub get_file_owner { + my $path = shift; + + my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); + my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); + if (!defined $gcos) { + return undef; + } + my $owner = $gcos; + $owner =~ s/[,;].*$//; + return decode("utf8", $owner, Encode::FB_DEFAULT); +} + +## ...................................................................... +## mimetype related functions + +sub mimetype_guess_file { + my $filename = shift; + my $mimemap = shift; + -r $mimemap or return undef; + + my %mimemap; + open(MIME, $mimemap) or return undef; + while (<MIME>) { + next if m/^#/; # skip comments + my ($mime, $exts) = split(/\t+/); + if (defined $exts) { + my @exts = split(/\s+/, $exts); + foreach my $ext (@exts) { + $mimemap{$ext} = $mime; + } + } + } + close(MIME); + + $filename =~ /\.(.*?)$/; + return $mimemap{$1}; +} + +sub mimetype_guess { + my $filename = shift; + my $mime; + $filename =~ /\./ or return undef; + + if ($mimetypes_file) { + my $file = $mimetypes_file; + if ($file !~ m!^/!) { # if it is relative path + # it is relative to project + $file = "$projectroot/$project/$file"; + } + $mime = mimetype_guess_file($filename, $file); + } + $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); + return $mime; +} + +sub blob_mimetype { + my $fd = shift; + my $filename = shift; + + if ($filename) { + my $mime = mimetype_guess($filename); + $mime and return $mime; + } + + # just in case + return $default_blob_plain_mimetype unless $fd; + + if (-T $fd) { + return 'text/plain' . + ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); + } elsif (! $filename) { + return 'application/octet-stream'; + } elsif ($filename =~ m/\.png$/i) { + return 'image/png'; + } elsif ($filename =~ m/\.gif$/i) { + return 'image/gif'; + } elsif ($filename =~ m/\.jpe?g$/i) { + return 'image/jpeg'; + } else { + return 'application/octet-stream'; + } +} + +## ====================================================================== +## functions printing HTML: header, footer, error page + +sub git_header_html { + my $status = shift || "200 OK"; + my $expires = shift; + + my $title = "$site_name git"; + if (defined $project) { + $title .= " - $project"; + if (defined $action) { + $title .= "/$action"; + if (defined $file_name) { + $title .= " - $file_name"; + if ($action eq "tree" && $file_name !~ m|/$|) { + $title .= "/"; + } + } + } + } + my $content_type; + # require explicit support from the UA if we are to send the page as + # 'application/xhtml+xml', otherwise send it as plain old 'text/html'. + # we have to do this because MSIE sometimes globs '*/*', pretending to + # support xhtml+xml but choking when it gets what it asked for. + if (defined $cgi->http('HTTP_ACCEPT') && $cgi->http('HTTP_ACCEPT') =~ m/(,|;|\s|^)application\/xhtml\+xml(,|;|\s|$)/ && $cgi->Accept('application/xhtml+xml') != 0) { + $content_type = 'application/xhtml+xml'; + } else { + $content_type = 'text/html'; + } + print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + print <<EOF; +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US" lang="en-US"> +<!-- git web interface version $version, (C) 2005-2006, Kay Sievers <kay.sievers\@vrfy.org>, Christian Gierke --> +<!-- git core binaries version $git_version --> +<head> +<meta http-equiv="content-type" content="$content_type; charset=utf-8"/> +<meta name="generator" content="gitweb/$version git/$git_version"/> +<meta name="robots" content="index, nofollow"/> +<title>$title</title> +<link rel="stylesheet" type="text/css" href="$stylesheet"/> +EOF + if (defined $project) { + printf('<link rel="alternate" title="%s log" '. + 'href="%s" type="application/rss+xml"/>'."\n", + esc_param($project), href(action=>"rss")); + } + + print "</head>\n" . + "<body>\n" . + "<div class=\"page_header\">\n" . + "<a href=\"http://www.kernel.org/pub/software/scm/git/docs/\" title=\"git documentation\">" . + "<img src=\"$logo\" width=\"72\" height=\"27\" alt=\"git\" style=\"float:right; border-width:0px;\"/>" . + "</a>\n"; + print $cgi->a({-href => esc_param($home_link)}, $home_link_str) . " / "; + if (defined $project) { + print $cgi->a({-href => href(action=>"summary")}, esc_html($project)); + if (defined $action) { + print " / $action"; + } + print "\n"; + if (!defined $searchtext) { + $searchtext = ""; + } + my $search_hash; + if (defined $hash_base) { + $search_hash = $hash_base; + } elsif (defined $hash) { + $search_hash = $hash; + } else { + $search_hash = "HEAD"; + } + $cgi->param("a", "search"); + $cgi->param("h", $search_hash); + print $cgi->startform(-method => "get", -action => $my_uri) . + "<div class=\"search\">\n" . + $cgi->hidden(-name => "p") . "\n" . + $cgi->hidden(-name => "a") . "\n" . + $cgi->hidden(-name => "h") . "\n" . + $cgi->textfield(-name => "s", -value => $searchtext) . "\n" . + "</div>" . + $cgi->end_form() . "\n"; + } + print "</div>\n"; +} + +sub git_footer_html { + print "<div class=\"page_footer\">\n"; + if (defined $project) { + my $descr = git_get_project_description($project); + if (defined $descr) { + print "<div class=\"page_footer_text\">" . esc_html($descr) . "</div>\n"; + } + print $cgi->a({-href => href(action=>"rss"), -class => "rss_logo"}, "RSS") . "\n"; + } else { + print $cgi->a({-href => "$my_uri?" . esc_param("a=opml"), -class => "rss_logo"}, "OPML") . "\n"; + } + print "</div>\n" . + "</body>\n" . + "</html>"; +} + +sub die_error { + my $status = shift || "403 Forbidden"; + my $error = shift || "Malformed query, file missing or permission denied"; + + git_header_html($status); + print "<div class=\"page_body\">\n" . + "<br/><br/>\n" . + "$status - $error\n" . + "<br/>\n" . + "</div>\n"; + git_footer_html(); + exit; +} + +## ---------------------------------------------------------------------- +## functions printing or outputting HTML: navigation + +sub git_print_page_nav { + my ($current, $suppress, $head, $treehead, $treebase, $extra) = @_; + $extra = '' if !defined $extra; # pager or formats + + my @navs = qw(summary shortlog log commit commitdiff tree); + if ($suppress) { + @navs = grep { $_ ne $suppress } @navs; + } + + my %arg = map { $_ => {action=>$_} } @navs; + if (defined $head) { + for (qw(commit commitdiff)) { + $arg{$_}{hash} = $head; + } + if ($current =~ m/^(tree | log | shortlog | commit | commitdiff | search)$/x) { + for (qw(shortlog log)) { + $arg{$_}{hash} = $head; + } + } + } + $arg{tree}{hash} = $treehead if defined $treehead; + $arg{tree}{hash_base} = $treebase if defined $treebase; + + print "<div class=\"page_nav\">\n" . + (join " | ", + map { $_ eq $current ? + $_ : $cgi->a({-href => href(%{$arg{$_}})}, "$_") + } @navs); + print "<br/>\n$extra<br/>\n" . + "</div>\n"; +} + +sub format_paging_nav { + my ($action, $hash, $head, $page, $nrevs) = @_; + my $paging_nav; + + + if ($hash ne $head || $page) { + $paging_nav .= $cgi->a({-href => href(action=>$action)}, "HEAD"); + } else { + $paging_nav .= "HEAD"; + } + + if ($page > 0) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page-1), + -accesskey => "p", -title => "Alt-p"}, "prev"); + } else { + $paging_nav .= " ⋅ prev"; + } + + if ($nrevs >= (100 * ($page+1)-1)) { + $paging_nav .= " ⋅ " . + $cgi->a({-href => href(action=>$action, hash=>$hash, page=>$page+1), + -accesskey => "n", -title => "Alt-n"}, "next"); + } else { + $paging_nav .= " ⋅ next"; + } + + return $paging_nav; +} + +## ...................................................................... +## functions printing or outputting HTML: div + +sub git_print_header_div { + my ($action, $title, $hash, $hash_base) = @_; + my %args = (); + + $args{action} = $action; + $args{hash} = $hash if $hash; + $args{hash_base} = $hash_base if $hash_base; + + print "<div class=\"header\">\n" . + $cgi->a({-href => href(%args), -class => "title"}, + $title ? $title : $action) . + "\n</div>\n"; +} + +sub git_print_page_path { + my $name = shift; + my $type = shift; + + if (!defined $name) { + print "<div class=\"page_path\"><b>/</b></div>\n"; + } elsif (defined $type && $type eq 'blob') { + print "<div class=\"page_path\"><b>" . + $cgi->a({-href => href(action=>"blob_plain", file_name=>$file_name)}, esc_html($name)) . "</b><br/></div>\n"; + } else { + print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; + } +} + +## ...................................................................... +## functions printing large fragments of HTML + +sub git_difftree_body { + my ($difftree, $parent) = @_; + + print "<div class=\"list_head\">\n"; + if ($#{$difftree} > 10) { + print(($#{$difftree} + 1) . " files changed:\n"); + } + print "</div>\n"; + + print "<table class=\"diff_tree\">\n"; + my $alternate = 0; + foreach my $line (@{$difftree}) { + # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' + # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/) { + next; + } + my $from_mode = $1; + my $to_mode = $2; + my $from_id = $3; + my $to_id = $4; + my $status = $5; + my $similarity = $6; # score + my $file = validate_input(unquote($7)); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + + if ($status eq "A") { # created + my $mode_chng = ""; + if (S_ISREG(oct $to_mode)) { + $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file), + -class => "list"}, esc_html($file)) . + "</td>\n" . + "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob") . + "</td>\n"; + + } elsif ($status eq "D") { # deleted + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$file), + -class => "list"}, esc_html($file)) . "</td>\n" . + "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$file)}, "blob") . " | " . + $cgi->a({-href => href(action=>"history", hash_base=>$parent, file_name=>$file)}, "history") . + "</td>\n" + + } elsif ($status eq "M" || $status eq "T") { # modified, or type changed + my $mode_chnge = ""; + if ($from_mode != $to_mode) { + $mode_chnge = " <span class=\"file_status mode_chnge\">[changed"; + if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { + $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); + } + if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { + if (S_ISREG($from_mode) && S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); + } elsif (S_ISREG($to_mode)) { + $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); + } + } + $mode_chnge .= "]</span>\n"; + } + print "<td>"; + if ($to_id ne $from_id) { # modified + print $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file), + -class => "list"}, esc_html($file)); + } else { # mode changed + print $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file), + -class => "list"}, esc_html($file)); + } + print "</td>\n" . + "<td>$mode_chnge</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$file)}, "blob"); + if ($to_id ne $from_id) { # modified + print $cgi->a({-href => href(action=>"blobdiff", hash=>$to_id, hash_parent=>$from_id, hash_base=>$hash, file_name=>$file)}, "diff"); + } + print " | " . $cgi->a({-href => href(action=>"history", hash_base=>$hash, file_name=>$file)}, "history") . "\n"; + print "</td>\n"; + + } elsif ($status eq "R") { # renamed + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file), + -class => "list"}, esc_html($to_file)) . "</td>\n" . + "<td><span class=\"file_status moved\">[moved from " . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$from_file), + -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); + if ($to_id ne $from_id) { + print " | " . + $cgi->a({-href => "$my_uri?" . + esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + } + print "</td>\n"; + + } elsif ($status eq "C") { # copied + my ($from_file, $to_file) = split "\t", $file; + my $mode_chng = ""; + if ($from_mode != $to_mode) { + $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); + } + print "<td>" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file), + -class => "list"}, esc_html($to_file)) . "</td>\n" . + "<td><span class=\"file_status copied\">[copied from " . + $cgi->a({-href => href(action=>"blob", hash=>$from_id, hash_base=>$parent, file_name=>$from_file), + -class => "list"}, esc_html($from_file)) . + " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"blob", hash=>$to_id, hash_base=>$hash, file_name=>$to_file)}, "blob"); + if ($to_id ne $from_id) { + print " | " . + $cgi->a({-href => "$my_uri?" . + esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file;fp=$from_file")}, "diff"); + } + print "</td>\n"; + } # we should not encounter Unmerged (U) or Unknown (X) status + print "</tr>\n"; + } + print "</table>\n"; +} + +sub git_shortlog_body { + # uses global variable $project + my ($revlist, $from, $to, $refs, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$revlist} if (!defined $to || $#{$revlist} < $to); + + print "<table class=\"shortlog\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $commit = $revlist->[$i]; + #my $ref = defined $refs ? format_ref_marker($refs, $commit) : ''; + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + # git_summary() used print "<td><i>$co{'age_string'}</i></td>\n" . + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . + "<td>"; + print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . + "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"4\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_history_body { + # Warning: assumes constant type (blob or tree) during history + my ($fd, $refs, $hash_base, $ftype, $extra) = @_; + + print "<table class=\"history\" cellspacing=\"0\">\n"; + my $alternate = 0; + while (my $line = <$fd>) { + if ($line !~ m/^([0-9a-fA-F]{40})/) { + next; + } + + my $commit = $1; + my %co = parse_commit($commit); + if (!%co) { + next; + } + + my $ref = format_ref_marker($refs, $commit); + + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . + # shortlog uses chop_str($co{'author_name'}, 10) + "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . + "<td>"; + # originally git_history used chop_str($co{'title'}, 50) + print format_subject_html($co{'title'}, $co{'title_short'}, href(action=>"commit", hash=>$commit), $ref); + print "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . " | " . + $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . " | " . + $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype); + + if ($ftype eq 'blob') { + my $blob_current = git_get_hash_by_path($hash_base, $file_name); + my $blob_parent = git_get_hash_by_path($commit, $file_name); + if (defined $blob_current && defined $blob_parent && + $blob_current ne $blob_parent) { + print " | " . + $cgi->a({-href => href(action=>"blobdiff", hash=>$blob_current, hash_parent=>$blob_parent, hash_base=>$commit, file_name=>$file_name)}, + "diff to current"); + } + } + print "</td>\n" . + "</tr>\n"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"4\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_tags_body { + # uses global variable $project + my ($taglist, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "<table class=\"tags\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $comment_lines = $tag{'comment'}; + my $comment = shift @$comment_lines; + my $comment_short; + if (defined $comment) { + $comment_short = chop_str($comment, 30, 5); + } + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td><i>$tag{'age'}</i></td>\n" . + "<td>" . + $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'}), + -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . + "</td>\n" . + "<td>"; + if (defined $comment) { + print format_subject_html($comment, $comment_short, href(action=>"tag", hash=>$tag{'id'})); + } + print "</td>\n" . + "<td class=\"selflink\">"; + if ($tag{'type'} eq "tag") { + print $cgi->a({-href => href(action=>"tag", hash=>$tag{'id'})}, "tag"); + } else { + print " "; + } + print "</td>\n" . + "<td class=\"link\">" . " | " . + $cgi->a({-href => href(action=>$tag{'reftype'}, hash=>$tag{'refid'})}, $tag{'reftype'}); + if ($tag{'reftype'} eq "commit") { + print " | " . $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . + " | " . $cgi->a({-href => href(action=>"log", hash=>$tag{'refid'})}, "log"); + } elsif ($tag{'reftype'} eq "blob") { + print " | " . $cgi->a({-href => href(action=>"blob_plain", hash=>$tag{'refid'})}, "raw"); + } + print "</td>\n" . + "</tr>"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"5\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +sub git_heads_body { + # uses global variable $project + my ($taglist, $head, $from, $to, $extra) = @_; + $from = 0 unless defined $from; + $to = $#{$taglist} if (!defined $to || $#{$taglist} < $to); + + print "<table class=\"heads\" cellspacing=\"0\">\n"; + my $alternate = 0; + for (my $i = $from; $i <= $to; $i++) { + my $entry = $taglist->[$i]; + my %tag = %$entry; + my $curr = $tag{'id'} eq $head; + if ($alternate) { + print "<tr class=\"dark\">\n"; + } else { + print "<tr class=\"light\">\n"; + } + $alternate ^= 1; + print "<td><i>$tag{'age'}</i></td>\n" . + ($tag{'id'} eq $head ? "<td class=\"current_head\">" : "<td>") . + $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'}), + -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . + "</td>\n" . + "<td class=\"link\">" . + $cgi->a({-href => href(action=>"shortlog", hash=>$tag{'name'})}, "shortlog") . " | " . + $cgi->a({-href => href(action=>"log", hash=>$tag{'name'})}, "log") . + "</td>\n" . + "</tr>"; + } + if (defined $extra) { + print "<tr>\n" . + "<td colspan=\"3\">$extra</td>\n" . + "</tr>\n"; + } + print "</table>\n"; +} + +## ---------------------------------------------------------------------- +## functions printing large fragments, format as one of arguments + sub git_diff_print { my $from = shift; my $from_name = shift; @@ -610,7 +1528,7 @@ sub git_diff_print { if (defined $from) { $from_tmp = "$git_temp/gitweb_" . $$ . "_from"; open my $fd2, "> $from_tmp"; - open my $fd, "-|", "$GIT cat-file blob $from"; + open my $fd, "-|", $GIT, "cat-file", "blob", $from; my @file = <$fd>; print $fd2 @file; close $fd2; @@ -621,7 +1539,7 @@ sub git_diff_print { if (defined $to) { $to_tmp = "$git_temp/gitweb_" . $$ . "_to"; open my $fd2, "> $to_tmp"; - open my $fd, "-|", "$GIT cat-file blob $to"; + open my $fd, "-|", $GIT, "cat-file", "blob", $to; my @file = <$fd>; print $fd2 @file; close $fd2; @@ -635,7 +1553,7 @@ sub git_diff_print { $/ = "\n"; } else { while (my $line = <$fd>) { - chomp($line); + chomp $line; my $char = substr($line, 0, 1); my $diff_class = ""; if ($char eq '+') { @@ -648,12 +1566,7 @@ sub git_diff_print { # skip errors next; } - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - (($pos-1) % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } + $line = untabify($line); print "<div class=\"diff$diff_class\">" . esc_html($line) . "</div>\n"; } } @@ -667,210 +1580,35 @@ sub git_diff_print { } } -sub mode_str { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return 'drwxr-xr-x'; - } elsif (S_ISLNK($mode)) { - return 'lrwxrwxrwx'; - } elsif (S_ISREG($mode)) { - # git cares only about the executable bit - if ($mode & S_IXUSR) { - return '-rwxr-xr-x'; - } else { - return '-rw-r--r--'; - }; - } else { - return '----------'; - } -} - -sub chop_str { - my $str = shift; - my $len = shift; - my $add_len = shift || 10; - - # allow only $len chars, but don't cut a word if it would fit in $add_len - # if it doesn't fit, cut it if it's still longer than the dots we would add - $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/; - my $body = $1; - my $tail = $2; - if (length($tail) > 4) { - $tail = " ..."; - } - return "$body$tail"; -} - -sub file_type { - my $mode = oct shift; - - if (S_ISDIR($mode & S_IFMT)) { - return "directory"; - } elsif (S_ISLNK($mode)) { - return "symlink"; - } elsif (S_ISREG($mode)) { - return "file"; - } else { - return "unknown"; - } -} - -sub format_log_line_html { - my $line = shift; - - $line = esc_html($line); - $line =~ s/ / /g; - if ($line =~ m/([0-9a-fA-F]{40})/) { - my $hash_text = $1; - if (git_get_type($hash_text) eq "commit") { - my $link = $cgi->a({-class => "text", -href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_text")}, $hash_text); - $line =~ s/$hash_text/$link/; - } - } - return $line; -} - -sub date_str { - my $epoch = shift; - my $tz = shift || "-0000"; - - my %date; - my @months = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"); - my @days = ("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"); - my ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($epoch); - $date{'hour'} = $hour; - $date{'minute'} = $min; - $date{'mday'} = $mday; - $date{'day'} = $days[$wday]; - $date{'month'} = $months[$mon]; - $date{'rfc2822'} = sprintf "%s, %d %s %4d %02d:%02d:%02d +0000", $days[$wday], $mday, $months[$mon], 1900+$year, $hour ,$min, $sec; - $date{'mday-time'} = sprintf "%d %s %02d:%02d", $mday, $months[$mon], $hour ,$min; - - $tz =~ m/^([+\-][0-9][0-9])([0-9][0-9])$/; - my $local = $epoch + ((int $1 + ($2/60)) * 3600); - ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday) = gmtime($local); - $date{'hour_local'} = $hour; - $date{'minute_local'} = $min; - $date{'tz_local'} = $tz; - return %date; -} - -# git-logo (cached in browser for one day) -sub git_logo { - binmode STDOUT, ':raw'; - print $cgi->header(-type => 'image/png', -expires => '+1d'); - # cat git-logo.png | hexdump -e '16/1 " %02x" "\n"' | sed 's/ /\\x/g' - print "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52" . - "\x00\x00\x00\x48\x00\x00\x00\x1b\x04\x03\x00\x00\x00\x2d\xd9\xd4" . - "\x2d\x00\x00\x00\x18\x50\x4c\x54\x45\xff\xff\xff\x60\x60\x5d\xb0" . - "\xaf\xaa\x00\x80\x00\xce\xcd\xc7\xc0\x00\x00\xe8\xe8\xe6\xf7\xf7" . - "\xf6\x95\x0c\xa7\x47\x00\x00\x00\x73\x49\x44\x41\x54\x28\xcf\x63" . - "\x48\x67\x20\x04\x4a\x5c\x18\x0a\x08\x2a\x62\x53\x61\x20\x02\x08" . - "\x0d\x69\x45\xac\xa1\xa1\x01\x30\x0c\x93\x60\x36\x26\x52\x91\xb1" . - "\x01\x11\xd6\xe1\x55\x64\x6c\x6c\xcc\x6c\x6c\x0c\xa2\x0c\x70\x2a" . - "\x62\x06\x2a\xc1\x62\x1d\xb3\x01\x02\x53\xa4\x08\xe8\x00\x03\x18" . - "\x26\x56\x11\xd4\xe1\x20\x97\x1b\xe0\xb4\x0e\x35\x24\x71\x29\x82" . - "\x99\x30\xb8\x93\x0a\x11\xb9\x45\x88\xc1\x8d\xa0\xa2\x44\x21\x06" . - "\x27\x41\x82\x40\x85\xc1\x45\x89\x20\x70\x01\x00\xa4\x3d\x21\xc5" . - "\x12\x1c\x9a\xfe\x00\x00\x00\x00\x49\x45\x4e\x44\xae\x42\x60\x82"; -} - -sub get_file_owner { - my $path = shift; - - my ($dev, $ino, $mode, $nlink, $st_uid, $st_gid, $rdev, $size) = stat($path); - my ($name, $passwd, $uid, $gid, $quota, $comment, $gcos, $dir, $shell) = getpwuid($st_uid); - if (!defined $gcos) { - return undef; - } - my $owner = $gcos; - $owner =~ s/[,;].*$//; - return decode("utf8", $owner, Encode::FB_DEFAULT); -} - -sub git_read_projects { - my @list; - - if (-d $projects_list) { - # search in directory - my $dir = $projects_list; - opendir my ($dh), $dir or return undef; - while (my $dir = readdir($dh)) { - if (-e "$projectroot/$dir/HEAD") { - my $pr = { - path => $dir, - }; - push @list, $pr - } - } - closedir($dh); - } elsif (-f $projects_list) { - # read from file(url-encoded): - # 'git%2Fgit.git Linus+Torvalds' - # 'libs%2Fklibc%2Fklibc.git H.+Peter+Anvin' - # 'linux%2Fhotplug%2Fudev.git Greg+Kroah-Hartman' - open my ($fd), $projects_list or return undef; - while (my $line = <$fd>) { - chomp $line; - my ($path, $owner) = split ' ', $line; - $path = unescape($path); - $owner = unescape($owner); - if (!defined $path) { - next; - } - if (-e "$projectroot/$path/HEAD") { - my $pr = { - path => $path, - owner => decode("utf8", $owner, Encode::FB_DEFAULT), - }; - push @list, $pr - } - } - close $fd; - } - @list = sort {$a->{'path'} cmp $b->{'path'}} @list; - return @list; -} - -sub git_get_project_config { - my $key = shift; - - return unless ($key); - $key =~ s/^gitweb\.//; - return if ($key =~ m/\W/); - my $val = qx($GIT repo-config --get gitweb.$key); - return ($val); -} +## ====================================================================== +## ====================================================================== +## actions -sub git_get_project_config_bool { - my $val = git_get_project_config (@_); - if ($val and $val =~ m/true|yes|on/) { - return (1); +sub git_project_list { + my $order = $cgi->param('o'); + if (defined $order && $order !~ m/project|descr|owner|age/) { + die_error(undef, "Unknown order parameter"); } - return; # implicit false -} -sub git_project_list { - my @list = git_read_projects(); + my @list = git_get_projects_list(); my @projects; if (!@list) { - die_error(undef, "No project found."); + die_error(undef, "No projects found"); } foreach my $pr (@list) { - my $head = git_read_head($pr->{'path'}); + my $head = git_get_head_hash($pr->{'path'}); if (!defined $head) { next; } $ENV{'GIT_DIR'} = "$projectroot/$pr->{'path'}"; - my %co = git_read_commit($head); + my %co = parse_commit($head); if (!%co) { next; } $pr->{'commit'} = \%co; if (!defined $pr->{'descr'}) { - my $descr = git_read_description($pr->{'path'}) || ""; + my $descr = git_get_project_description($pr->{'path'}) || ""; $pr->{'descr'} = chop_str($descr, 25, 5); } if (!defined $pr->{'owner'}) { @@ -878,6 +1616,7 @@ sub git_project_list { } push @projects, $pr; } + git_header_html(); if (-f $home_text) { print "<div class=\"index_include\">\n"; @@ -888,29 +1627,42 @@ sub git_project_list { } print "<table class=\"project_list\">\n" . "<tr>\n"; - if (!defined($order) || (defined($order) && ($order eq "project"))) { + $order ||= "project"; + if ($order eq "project") { @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; print "<th>Project</th>\n"; } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=project")}, "Project") . "</th>\n"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=project"), + -class => "header"}, "Project") . + "</th>\n"; } - if (defined($order) && ($order eq "descr")) { + if ($order eq "descr") { @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; print "<th>Description</th>\n"; } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=descr")}, "Description") . "</th>\n"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=descr"), + -class => "header"}, "Description") . + "</th>\n"; } - if (defined($order) && ($order eq "owner")) { + if ($order eq "owner") { @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; print "<th>Owner</th>\n"; } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=owner")}, "Owner") . "</th>\n"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=owner"), + -class => "header"}, "Owner") . + "</th>\n"; } - if (defined($order) && ($order eq "age")) { + if ($order eq "age") { @projects = sort {$a->{'commit'}{'age'} <=> $b->{'commit'}{'age'}} @projects; print "<th>Last Change</th>\n"; } else { - print "<th>" . $cgi->a({-class => "header", -href => "$my_uri?" . esc_param("o=age")}, "Last Change") . "</th>\n"; + print "<th>" . + $cgi->a({-href => "$my_uri?" . esc_param("o=age"), + -class => "header"}, "Last Change") . + "</th>\n"; } print "<th></th>\n" . "</tr>\n"; @@ -922,14 +1674,16 @@ sub git_project_list { print "<tr class=\"light\">\n"; } $alternate ^= 1; - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . - "<td>$pr->{'descr'}</td>\n" . + print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary"), + -class => "list"}, esc_html($pr->{'path'})) . "</td>\n" . + "<td>" . esc_html($pr->{'descr'}) . "</td>\n" . "<td><i>" . chop_str($pr->{'owner'}, 15) . "</i></td>\n"; - print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . $pr->{'commit'}{'age_string'} . "</td>\n" . + print "<td class=\"". age_class($pr->{'commit'}{'age'}) . "\">" . + $pr->{'commit'}{'age_string'} . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=summary")}, "summary") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=shortlog")}, "shortlog") . " | " . + $cgi->a({-href => "$my_uri?" . esc_param("p=$pr->{'path'};a=log")}, "log") . "</td>\n" . "</tr>\n"; } @@ -937,307 +1691,75 @@ sub git_project_list { git_footer_html(); } -sub read_info_ref { - my $type = shift || ""; - my %refs; - # 5dc01c595e6c6ec9ccda4f6f69c131c0dd945f8c refs/tags/v2.6.11 - # c39ae07f393806ccf406ef966e9a15afc43cc36a refs/tags/v2.6.11^{} - open my $fd, "$projectroot/$project/info/refs" or return; - while (my $line = <$fd>) { - chomp($line); - if ($line =~ m/^([0-9a-fA-F]{40})\t.*$type\/([^\^]+)/) { - if (defined $refs{$1}) { - $refs{$1} .= " / $2"; - } else { - $refs{$1} = $2; - } - } - } - close $fd or return; - return \%refs; -} - -sub git_read_refs { - my $ref_dir = shift; - my @reflist; - - my @refs; - opendir my $dh, "$projectroot/$project/$ref_dir"; - while (my $dir = readdir($dh)) { - if ($dir =~ m/^\./) { - next; - } - if (-d "$projectroot/$project/$ref_dir/$dir") { - opendir my $dh2, "$projectroot/$project/$ref_dir/$dir"; - my @subdirs = grep !m/^\./, readdir $dh2; - closedir($dh2); - foreach my $subdir (@subdirs) { - push @refs, "$dir/$subdir" - } - next; - } - push @refs, $dir; - } - closedir($dh); - foreach my $ref_file (@refs) { - my $ref_id = git_read_hash("$project/$ref_dir/$ref_file"); - my $type = git_get_type($ref_id) || next; - my %ref_item; - my %co; - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = git_read_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - %co = git_read_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - %co = git_read_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } - - push @reflist, \%ref_item; - } - # sort tags by age - @reflist = sort {$b->{'epoch'} <=> $a->{'epoch'}} @reflist; - return \@reflist; -} - sub git_summary { - my $descr = git_read_description($project) || "none"; - my $head = git_read_head($project); - my %co = git_read_commit($head); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + my $descr = git_get_project_description($project) || "none"; + my $head = git_get_head_hash($project); + my %co = parse_commit($head); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my $owner; - if (-f $projects_list) { - open (my $fd , $projects_list); - while (my $line = <$fd>) { - chomp $line; - my ($pr, $ow) = split ' ', $line; - $pr = unescape($pr); - $ow = unescape($ow); - if ($pr eq $project) { - $owner = decode("utf8", $ow, Encode::FB_DEFAULT); - last; - } - } - close $fd; - } - if (!defined $owner) { - $owner = get_file_owner("$projectroot/$project"); - } + my $owner = git_get_project_owner($project); - my $refs = read_info_ref(); + my $refs = git_get_references(); git_header_html(); - print "<div class=\"page_nav\">\n" . - "summary". - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; + git_print_page_nav('summary','', $head); + print "<div class=\"title\"> </div>\n"; print "<table cellspacing=\"0\">\n" . "<tr><td>description</td><td>" . esc_html($descr) . "</td></tr>\n" . "<tr><td>owner</td><td>$owner</td></tr>\n" . - "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n" . - "</table>\n"; - open my $fd, "-|", "$GIT rev-list --max-count=17 " . git_read_head($project) or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog"), -class => "title"}, "shortlog") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $commit (@revlist) { - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - print "<td><i>$co{'age_string'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . - "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "<b>" . esc_html($co{'title'}) . "$ref</b>"); - } - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "</td>\n" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "...") . "</td>\n" . - "</tr>"; - last; - } + "<tr><td>last change</td><td>$cd{'rfc2822'}</td></tr>\n"; + # use per project git URL list in $projectroot/$project/cloneurl + # or make project git URL from git base URL and project name + my $url_tag = "URL"; + my @url_list = git_get_project_url_list($project); + @url_list = map { "$_/$project" } @git_base_url_list unless @url_list; + foreach my $git_url (@url_list) { + next unless $git_url; + print "<tr><td>$url_tag</td><td>$git_url</td></tr>\n"; + $url_tag = ""; } - print "</table\n>"; + print "</table>\n"; + + open my $fd, "-|", $GIT, "rev-list", "--max-count=17", git_get_head_hash($project) + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd; + git_print_header_div('shortlog'); + git_shortlog_body(\@revlist, 0, 15, $refs, + $cgi->a({-href => href(action=>"shortlog")}, "...")); - my $taglist = git_read_refs("refs/tags"); + my $taglist = git_get_refs_list("refs/tags"); if (defined @$taglist) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags"), -class => "title"}, "tags") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $entry (@$taglist) { - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - if (defined($comment)) { - $comment = chop_str($comment, 30, 5); - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - print "<td><i>$tag{'age'}</i></td>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"}, - "<b>" . esc_html($tag{'name'}) . "</b>") . - "</td>\n" . - "<td>"; - if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, esc_html($comment)); - } - print "</td>\n" . - "<td class=\"link\">"; - if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); - if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); - } - print "</td>\n" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tags")}, "...") . "</td>\n" . - "</tr>"; - last; - } - } - print "</table\n>"; + git_print_header_div('tags'); + git_tags_body($taglist, 0, 15, + $cgi->a({-href => href(action=>"tags")}, "...")); } - my $headlist = git_read_refs("refs/heads"); + my $headlist = git_get_refs_list("refs/heads"); if (defined @$headlist) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads"), -class => "title"}, "heads") . - "</div>\n"; - my $i = 16; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - foreach my $entry (@$headlist) { - my %tag = %$entry; - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($i-- > 0) { - print "<td><i>$tag{'age'}</i></td>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, - "<b>" . esc_html($tag{'name'}) . "</b>") . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . - "</td>\n" . - "</tr>"; - } else { - print "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=heads")}, "...") . "</td>\n" . - "</tr>"; - last; - } - } - print "</table\n>"; + git_print_header_div('heads'); + git_heads_body($headlist, $head, 0, 15, + $cgi->a({-href => href(action=>"heads")}, "...")); } - git_footer_html(); -} -sub git_print_page_path { - my $name = shift; - my $type = shift; - - if (!defined $name) { - print "<div class=\"page_path\"><b>/</b></div>\n"; - } elsif ($type =~ "blob") { - print "<div class=\"page_path\"><b>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;f=$file_name")}, esc_html($name)) . "</b><br/></div>\n"; - } else { - print "<div class=\"page_path\"><b>" . esc_html($name) . "</b><br/></div>\n"; - } + git_footer_html(); } sub git_tag { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my %tag = git_read_tag($hash); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($tag{'name'})) . "\n" . - "</div>\n"; + git_print_page_nav('','', $head,undef,$head); + my %tag = parse_tag($hash); + git_print_header_div('commit', esc_html($tag{'name'}), $hash); print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n" . "<tr>\n" . "<td>object</td>\n" . - "<td>" . $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'object'}) . "</td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'type'};h=$tag{'object'}")}, $tag{'type'}) . "</td>\n" . + "<td>" . $cgi->a({-class => "list", -href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'object'}) . "</td>\n" . + "<td class=\"link\">" . $cgi->a({-href => href(action=>$tag{'type'}, hash=>$tag{'object'})}, $tag{'type'}) . "</td>\n" . "</tr>\n"; if (defined($tag{'author'})) { - my %ad = date_str($tag{'epoch'}, $tag{'tz'}); + my %ad = parse_date($tag{'epoch'}, $tag{'tz'}); print "<tr><td>author</td><td>" . esc_html($tag{'author'}) . "</td></tr>\n"; print "<tr><td></td><td>" . $ad{'rfc2822'} . sprintf(" (%02d:%02d %s)", $ad{'hour_local'}, $ad{'minute_local'}, $ad{'tz_local'}) . "</td></tr>\n"; } @@ -1255,11 +1777,11 @@ sub git_tag { sub git_blame2 { my $fd; my $ftype; - die_error(undef, "Permission denied.") if (!git_get_project_config_bool ('blame')); + die_error(undef, "Permission denied") if (!git_get_project_config_bool ('blame')); die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed") unless ($hash_base); - my %co = git_read_commit($hash_base) + $hash_base ||= git_get_head_hash($project); + die_error(undef, "Couldn't find base commit") unless ($hash_base); + my %co = parse_commit($hash_base) or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") @@ -1267,26 +1789,18 @@ sub git_blame2 { } $ftype = git_get_type($hash); if ($ftype !~ "blob") { - die_error("400 Bad Request", "object is not a blob"); + die_error("400 Bad Request", "Object is not a blob"); } open ($fd, "-|", $GIT, "blame", '-l', $file_name, $hash_base) - or die_error(undef, "Open failed"); + or die_error(undef, "Open git-blame failed"); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n"; - print "</div>\n". - "<div>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "</div>\n"; + my $formats_nav = + $cgi->a({-href => href(action=>"blobl", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); git_print_page_path($file_name, $ftype); - my @rev_color = (qw(light dark)); + my @rev_color = (qw(light2 dark2)); my $num_colors = scalar(@rev_color); my $current_color = 0; my $last_rev; @@ -1308,7 +1822,7 @@ sub git_blame2 { } print "<tr class=\"$rev_color[$current_color]\">\n"; print "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$full_rev;f=$file_name")}, esc_html($rev)) . "</td>\n"; + $cgi->a({-href => href(action=>"commit", hash=>$full_rev, file_name=>$file_name)}, esc_html($rev)) . "</td>\n"; print "<td class=\"linenr\"><a id=\"l$lineno\" href=\"#l$lineno\" class=\"linenr\">" . esc_html($lineno) . "</a></td>\n"; print "<td class=\"pre\">" . esc_html($data) . "</td>\n"; print "</tr>\n"; @@ -1321,33 +1835,25 @@ sub git_blame2 { sub git_blame { my $fd; - die_error('403 Permission denied', "Permission denied.") if (!git_get_project_config_bool ('blame')); - die_error('404 Not Found', "What file will it be, master?") if (!$file_name); - $hash_base ||= git_read_head($project); - die_error(undef, "Reading commit failed.") unless ($hash_base); - my %co = git_read_commit($hash_base) - or die_error(undef, "Reading commit failed."); + die_error('403 Permission denied', "Permission denied") if (!git_get_project_config_bool ('blame')); + die_error('404 Not Found', "File name not defined") if (!$file_name); + $hash_base ||= git_get_head_hash($project); + die_error(undef, "Couldn't find base commit") unless ($hash_base); + my %co = parse_commit($hash_base) + or die_error(undef, "Reading commit failed"); if (!defined $hash) { $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error lookup file."); + or die_error(undef, "Error lookup file"); } open ($fd, "-|", $GIT, "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open failed."); + or die_error(undef, "Open git-annotate failed"); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$hash;hb=$hash_base;f=$file_name")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;f=$file_name")}, "head") . "<br/>\n"; - print "</div>\n". - "<div>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "</div>\n"; - git_print_page_path($file_name); + my $formats_nav = + $cgi->a({-href => href(action=>"blobl", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blob") . + " | " . $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, "head"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); + git_print_page_path($file_name, 'blob'); print "<div class=\"page_body\">\n"; print <<HTML; <table class="blame"> @@ -1393,18 +1899,13 @@ HTML $age_class = age_class($age); $author = esc_html ($author); $author =~ s/ / /g; - # escape tabs - while ((my $pos = index($data, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $data =~ s/\t/$spaces/; - } - } + + $data = untabify($data); $data = esc_html ($data); print <<HTML; <tr class="$line_class[$line_class_num]"> - <td class="sha1"><a href="$my_uri?${\esc_param ("p=$project;a=commit;h=$long_rev")}" class="text">$short_rev..</a></td> + <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td> <td class="$age_class">$age_str</td> <td>$author</td> <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td> @@ -1419,211 +1920,46 @@ HTML } sub git_tags { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my $taglist = git_read_refs("refs/tags"); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + my $taglist = git_get_refs_list("refs/tags"); if (defined @$taglist) { - foreach my $entry (@$taglist) { - my %tag = %$entry; - my $comment_lines = $tag{'comment'}; - my $comment = shift @$comment_lines; - if (defined($comment)) { - $comment = chop_str($comment, 30, 5); - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td><i>$tag{'age'}</i></td>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}"), -class => "list"}, - "<b>" . esc_html($tag{'name'}) . "</b>") . - "</td>\n" . - "<td>"; - if (defined($comment)) { - print $cgi->a({-class => "list", -href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, $comment); - } - print "</td>\n" . - "<td class=\"link\">"; - if ($tag{'type'} eq "tag") { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tag;h=$tag{'id'}")}, "tag") . " | "; - } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=$tag{'reftype'};h=$tag{'refid'}")}, $tag{'reftype'}); - if ($tag{'reftype'} eq "commit") { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'refid'}")}, "log"); - } - print "</td>\n" . - "</tr>"; - } + git_tags_body($taglist); } - print "</table\n>"; git_footer_html(); } sub git_heads { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$head")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$head")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;hb=$head")}, "tree") . "<br/>\n" . - "<br/>\n" . - "</div>\n"; - my $taglist = git_read_refs("refs/heads"); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; + git_print_page_nav('','', $head,undef,$head); + git_print_header_div('summary', $project); + + my $taglist = git_get_refs_list("refs/heads"); if (defined @$taglist) { - foreach my $entry (@$taglist) { - my %tag = %$entry; - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td><i>$tag{'age'}</i></td>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}"), -class => "list"}, "<b>" . esc_html($tag{'name'}) . "</b>") . - "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$tag{'name'}")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$tag{'name'}")}, "log") . - "</td>\n" . - "</tr>"; - } + git_heads_body($taglist, $head); } - print "</table\n>"; git_footer_html(); } -sub git_get_hash_by_path { - my $base = shift; - my $path = shift || return undef; - - my $tree = $base; - my @parts = split '/', $path; - while (my $part = shift @parts) { - open my $fd, "-|", "$GIT ls-tree $tree" or die_error(undef, "Open git-ls-tree failed."); - my (@entries) = map { chomp; $_ } <$fd>; - close $fd or return undef; - foreach my $line (@entries) { - #'100644 blob 0fa3f3a66fb6a137f6ec2c19351ed4d807070ffa panic.c' - $line =~ m/^([0-9]+) (.+) ([0-9a-fA-F]{40})\t(.+)$/; - my $t_mode = $1; - my $t_type = $2; - my $t_hash = $3; - my $t_name = validate_input(unquote($4)); - if ($t_name eq $part) { - if (!(@parts)) { - return $t_hash; - } - if ($t_type eq "tree") { - $tree = $t_hash; - } - last; - } - } - } -} - -sub mimetype_guess_file { - my $filename = shift; - my $mimemap = shift; - -r $mimemap or return undef; - - my %mimemap; - open(MIME, $mimemap) or return undef; - while (<MIME>) { - my ($mime, $exts) = split(/\t+/); - my @exts = split(/\s+/, $exts); - foreach my $ext (@exts) { - $mimemap{$ext} = $mime; - } - } - close(MIME); - - $filename =~ /\.(.*?)$/; - return $mimemap{$1}; -} - -sub mimetype_guess { - my $filename = shift; - my $mime; - $filename =~ /\./ or return undef; - - if ($mimetypes_file) { - my $file = $mimetypes_file; - #$file =~ m#^/# or $file = "$projectroot/$path/$file"; - $mime = mimetype_guess_file($filename, $file); - } - $mime ||= mimetype_guess_file($filename, '/etc/mime.types'); - return $mime; -} - -sub git_blob_plain_mimetype { - my $fd = shift; - my $filename = shift; - - if ($filename) { - my $mime = mimetype_guess($filename); - $mime and return $mime; - } - - # just in case - return $default_blob_plain_mimetype unless $fd; - - if (-T $fd) { - return 'text/plain' . - ($default_text_plain_charset ? '; charset='.$default_text_plain_charset : ''); - } elsif (! $filename) { - return 'application/octet-stream'; - } elsif ($filename =~ m/\.png$/i) { - return 'image/png'; - } elsif ($filename =~ m/\.gif$/i) { - return 'image/gif'; - } elsif ($filename =~ m/\.jpe?g$/i) { - return 'image/jpeg'; - } else { - return 'application/octet-stream'; - } -} - sub git_blob_plain { if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } + if (defined $file_name) { + my $base = $hash_base || git_get_head_hash($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); + } + } my $type = shift; - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error("Couldn't cat $file_name, $hash"); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash"); - $type ||= git_blob_plain_mimetype($fd, $file_name); + $type ||= blob_mimetype($fd, $file_name); # save as filename, even when no $file_name is given my $save_as = "$hash"; @@ -1644,42 +1980,37 @@ sub git_blob_plain { sub git_blob { if (!defined $hash) { - if (defined $file_name) { - my $base = $hash_base || git_read_head($project); - $hash = git_get_hash_by_path($base, $file_name, "blob") || die_error(undef, "Error lookup file."); - } else { - die_error(undef, "No file name defined."); - } - } + if (defined $file_name) { + my $base = $hash_base || git_get_head_hash($project); + $hash = git_get_hash_by_path($base, $file_name, "blob") + or die_error(undef, "Error lookup file"); + } else { + die_error(undef, "No file name defined"); + } + } my $have_blame = git_get_project_config_bool ('blame'); - open my $fd, "-|", "$GIT cat-file blob $hash" or die_error(undef, "Open failed."); - my $mimetype = git_blob_plain_mimetype($fd, $file_name); + open my $fd, "-|", $GIT, "cat-file", "blob", $hash + or die_error(undef, "Couldn't cat $file_name, $hash"); + my $mimetype = blob_mimetype($fd, $file_name); if ($mimetype !~ m/^text\//) { close $fd; return git_blob_plain($mimetype); } git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . "<br/>\n"; + my $formats_nav = ''; + if (defined $hash_base && (my %co = parse_commit($hash_base))) { if (defined $file_name) { if ($have_blame) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$hash;hb=$hash_base;f=$file_name")}, "blame") . " | "; + $formats_nav .= $cgi->a({-href => href(action=>"blame", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, "blame") . " | "; } - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash;f=$file_name")}, "plain") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=HEAD;f=$file_name")}, "head") . "<br/>\n"; + $formats_nav .= + $cgi->a({-href => href(action=>"blob_plain", hash=>$hash, file_name=>$file_name)}, "plain") . + " | " . $cgi->a({-href => href(action=>"blob", hash_base=>"HEAD", file_name=>$file_name)}, "head"); } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$hash")}, "plain") . "<br/>\n"; + $formats_nav .= $cgi->a({-href => href(action=>"blob_plain", hash=>$hash)}, "plain"); } - print "</div>\n". - "<div>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . - "</div>\n"; + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . @@ -1691,12 +2022,7 @@ sub git_blob { while (my $line = <$fd>) { chomp $line; $nr++; - while ((my $pos = index($line, "\t")) != -1) { - if (my $count = (8 - ($pos % 8))) { - my $spaces = ' ' x $count; - $line =~ s/\t/$spaces/; - } - } + $line = untabify($line); printf "<div class=\"pre\"><a id=\"l%i\" href=\"#l%i\" class=\"linenr\">%4i</a> %s</div>\n", $nr, $nr, $nr, esc_html($line); } close $fd or print "Reading blob failed.\n"; @@ -1706,7 +2032,7 @@ sub git_blob { sub git_tree { if (!defined $hash) { - $hash = git_read_head($project); + $hash = git_get_head_hash($project); if (defined $file_name) { my $base = $hash_base || $hash; $hash = git_get_hash_by_path($base, $file_name, "tree"); @@ -1716,33 +2042,22 @@ sub git_tree { } } $/ = "\0"; - open my $fd, "-|", "$GIT ls-tree -z $hash" or die_error(undef, "Open git-ls-tree failed."); - chomp (my (@entries) = <$fd>); - close $fd or die_error(undef, "Reading tree failed."); + open my $fd, "-|", $GIT, "ls-tree", '-z', $hash + or die_error(undef, "Open git-ls-tree failed"); + my @entries = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading tree failed"); $/ = "\n"; - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$hash_base}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$hash_base}) . "</span>"; - } + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $hash_base); git_header_html(); my $base_key = ""; my $base = ""; - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { + my $have_blame = git_get_project_config_bool ('blame'); + if (defined $hash_base && (my %co = parse_commit($hash_base))) { $base_key = ";hb=$hash_base"; - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash_base")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash_base")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | tree" . - "<br/><br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "</div>\n"; + git_print_page_nav('tree','', $hash_base); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash_base); } else { print "<div class=\"page_nav\">\n"; print "<br/><br/></div>\n"; @@ -1751,7 +2066,7 @@ sub git_tree { if (defined $file_name) { $base = esc_html("$file_name/"); } - git_print_page_path($file_name); + git_print_page_path($file_name, 'tree'); print "<div class=\"page_body\">\n"; print "<table cellspacing=\"0\">\n"; my $alternate = 0; @@ -1774,9 +2089,11 @@ sub git_tree { $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name"), -class => "list"}, esc_html($t_name)) . "</td>\n" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob") . -# " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$t_hash$base_key;f=$base$t_name")}, "blob"); + if ($have_blame) { + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;h=$t_hash$base_key;f=$base$t_name")}, "blame"); + } + print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;h=$t_hash;hb=$hash_base;f=$base$t_name")}, "history") . " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob_plain;h=$t_hash;f=$base$t_name")}, "raw") . "</td>\n"; } elsif ($t_type eq "tree") { @@ -1795,163 +2112,47 @@ sub git_tree { git_footer_html(); } -sub git_rss { - # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ - open my $fd, "-|", "$GIT rev-list --max-count=150 " . git_read_head($project) or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading rev-list failed."); - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". - "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; - print "<channel>\n"; - print "<title>$project</title>\n". - "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". - "<description>$project log</description>\n". - "<language>en</language>\n"; - - for (my $i = 0; $i <= $#revlist; $i++) { - my $commit = $revlist[$i]; - my %co = git_read_commit($commit); - # we read 150, we always show 30 and the ones more recent than 48 hours - if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { - last; - } - my %cd = date_str($co{'committer_epoch'}); - open $fd, "-|", "$GIT diff-tree -r $co{'parent'} $co{'id'}" or next; - my @difftree = map { chomp; $_ } <$fd>; - close $fd or next; - print "<item>\n" . - "<title>" . - sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . - "</title>\n" . - "<author>" . esc_html($co{'author'}) . "</author>\n" . - "<pubDate>$cd{'rfc2822'}</pubDate>\n" . - "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . - "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . - "<description>" . esc_html($co{'title'}) . "</description>\n" . - "<content:encoded>" . - "<![CDATA[\n"; - my $comment = $co{'comment'}; - foreach my $line (@$comment) { - $line = decode("utf8", $line, Encode::FB_DEFAULT); - print "$line<br/>\n"; - } - print "<br/>\n"; - foreach my $line (@difftree) { - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $file = validate_input(unquote($7)); - $file = decode("utf8", $file, Encode::FB_DEFAULT); - print "$file<br/>\n"; - } - print "]]>\n" . - "</content:encoded>\n" . - "</item>\n"; - } - print "</channel></rss>"; -} - -sub git_opml { - my @list = git_read_projects(); - - print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); - print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". - "<opml version=\"1.0\">\n". - "<head>". - " <title>$site_name Git OPML Export</title>\n". - "</head>\n". - "<body>\n". - "<outline text=\"git RSS feeds\">\n"; - - foreach my $pr (@list) { - my %proj = %$pr; - my $head = git_read_head($proj{'path'}); - if (!defined $head) { - next; - } - $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; - my %co = git_read_commit($head); - if (!%co) { - next; - } - - my $path = esc_html(chop_str($proj{'path'}, 25, 5)); - my $rss = "$my_url?p=$proj{'path'};a=rss"; - my $html = "$my_url?p=$proj{'path'};a=summary"; - print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; - } - print "</outline>\n". - "</body>\n". - "</opml>\n"; -} - sub git_log { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | log" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n"; + my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; close $fd; - if ($hash ne $head || $page) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "HEAD"); - } else { - print "HEAD"; - } - if ($page > 0) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - print " ⋅ prev"; - } - if ($#revlist >= (100 * ($page+1)-1)) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - print " ⋅ next"; - } - print "<br/>\n" . - "</div>\n"; + my $paging_nav = format_paging_nav('log', $hash, $head, $page, $#revlist); + + git_header_html(); + git_print_page_nav('log','', $hash,undef,undef, $paging_nav); + if (!@revlist) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - my %co = git_read_commit($hash); + my %co = parse_commit($hash); + + git_print_header_div('summary', $project); print "<div class=\"page_body\"> Last change $co{'age_string'}.<br/><br/></div>\n"; } for (my $i = ($page * 100); $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - my %co = git_read_commit($commit); + my $ref = format_ref_marker($refs, $commit); + my %co = parse_commit($commit); next if !%co; - my %ad = date_str($co{'author_epoch'}); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "title"}, - "<span class=\"age\">$co{'age_string'}</span>" . esc_html($co{'title'}) . $ref) . "\n"; - print "</div>\n"; + my %ad = parse_date($co{'author_epoch'}); + git_print_header_div('commit', + "<span class=\"age\">$co{'age_string'}</span>" . + esc_html($co{'title'}) . $ref, + $commit); print "<div class=\"title_text\">\n" . "<div class=\"log_link\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . + $cgi->a({-href => href(action=>"commit", hash=>$commit)}, "commit") . + " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff") . "<br/>\n" . "</div>\n" . "<i>" . esc_html($co{'author_name'}) . " [$ad{'rfc2822'}]</i><br/>\n" . @@ -1982,59 +2183,43 @@ sub git_log { } sub git_commit { - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); - my %cd = date_str($co{'committer_epoch'}, $co{'committer_tz'}); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); + my %cd = parse_date($co{'committer_epoch'}, $co{'committer_tz'}); - my @difftree; - my $root = ""; my $parent = $co{'parent'}; if (!defined $parent) { - $root = " --root"; - $parent = ""; + $parent = "--root"; } - open my $fd, "-|", "$GIT diff-tree -r -M $root $parent $hash" or die_error(undef, "Open failed."); - @difftree = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); + open my $fd, "-|", $GIT, "diff-tree", '-r', '-M', $parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } - git_header_html(undef, $expires); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | commit"; - if (defined $co{'parent'}) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "\n" . - "<br/>\n"; + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + my $formats_nav = ''; if (defined $file_name && defined $co{'parent'}) { my $parent = $co{'parent'}; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blame;hb=$parent;f=$file_name")}, "blame") . "\n"; + $formats_nav .= $cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)}, "blame"); } - print "<br/></div>\n"; + git_header_html(undef, $expires); + git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff', + $hash, $co{'tree'}, $hash, + $formats_nav); if (defined $co{'parent'}) { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "</div>\n"; + git_print_header_div('commitdiff', esc_html($co{'title'}) . $ref, $hash); } else { - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; + git_print_header_div('tree', esc_html($co{'title'}) . $ref, $co{'tree'}, $hash); } print "<div class=\"title_text\">\n" . "<table cellspacing=\"0\">\n"; @@ -2054,19 +2239,19 @@ sub git_commit { print "<tr>" . "<td>tree</td>" . "<td class=\"sha1\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash"), class => "list"}, $co{'tree'}) . + $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash), class => "list"}, $co{'tree'}) . "</td>" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . + "<td class=\"link\">" . $cgi->a({-href => href(action=>"tree", hash=>$co{'tree'}, hash_base=>$hash)}, "tree") . "</td>" . "</tr>\n"; my $parents = $co{'parents'}; foreach my $par (@$parents) { print "<tr>" . "<td>parent</td>" . - "<td class=\"sha1\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par"), class => "list"}, $par) . "</td>" . + "<td class=\"sha1\">" . $cgi->a({-href => href(action=>"commit", hash=>$par), class => "list"}, $par) . "</td>" . "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$par")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash;hp=$par")}, "commitdiff") . + $cgi->a({-href => href(action=>"commit", hash=>$par)}, "commit") . + " | " . $cgi->a({-href => href(action=>"commitdiff", hash=>$hash, hash_parent=>$par)}, "commitdiff") . "</td>" . "</tr>\n"; } @@ -2095,121 +2280,20 @@ sub git_commit { } } print "</div>\n"; - print "<div class=\"list_head\">\n"; - if ($#difftree > 10) { - print(($#difftree + 1) . " files changed:\n"); - } - print "</div>\n"; - print "<table class=\"diff_tree\">\n"; - my $alternate = 0; - foreach my $line (@difftree) { - # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' - # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { - next; - } - my $from_mode = $1; - my $to_mode = $2; - my $from_id = $3; - my $to_id = $4; - my $status = $5; - my $similarity = $6; - my $file = validate_input(unquote($7)); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - if ($status eq "A") { - my $mode_chng = ""; - if (S_ISREG(oct $to_mode)) { - $mode_chng = sprintf(" with mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status new\">[new " . file_type($to_mode) . "$mode_chng]</span></td>\n" . - "<td class=\"link\">" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob") . "</td>\n"; - } elsif ($status eq "D") { - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)) . "</td>\n" . - "<td><span class=\"file_status deleted\">[deleted " . file_type($from_mode). "]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, "blob") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . - "</td>\n" - } elsif ($status eq "M" || $status eq "T") { - my $mode_chnge = ""; - if ($from_mode != $to_mode) { - $mode_chnge = " <span class=\"file_status mode_chnge\">[changed"; - if (((oct $from_mode) & S_IFMT) != ((oct $to_mode) & S_IFMT)) { - $mode_chnge .= " from " . file_type($from_mode) . " to " . file_type($to_mode); - } - if (((oct $from_mode) & 0777) != ((oct $to_mode) & 0777)) { - if (S_ISREG($from_mode) && S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o->%04o", (oct $from_mode) & 0777, (oct $to_mode) & 0777); - } elsif (S_ISREG($to_mode)) { - $mode_chnge .= sprintf(" mode: %04o", (oct $to_mode) & 0777); - } - } - $mode_chnge .= "]</span>\n"; - } - print "<td>"; - if ($to_id ne $from_id) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file"), -class => "list"}, esc_html($file)); - } - print "</td>\n" . - "<td>$mode_chnge</td>\n" . - "<td class=\"link\">"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$file")}, "diff"); - } - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=history;hb=$hash;f=$file")}, "history") . "\n"; - print "</td>\n"; - } elsif ($status eq "R") { - my ($from_file, $to_file) = split "\t", $file; - my $mode_chng = ""; - if ($from_mode != $to_mode) { - $mode_chng = sprintf(", mode: %04o", (oct $to_mode) & 0777); - } - print "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file"), -class => "list"}, esc_html($to_file)) . "</td>\n" . - "<td><span class=\"file_status moved\">[moved from " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$from_file"), -class => "list"}, esc_html($from_file)) . - " with " . (int $similarity) . "% similarity$mode_chng]</span></td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$to_file")}, "blob"); - if ($to_id ne $from_id) { - print " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$to_id;hp=$from_id;hb=$hash;f=$to_file")}, "diff"); - } - print "</td>\n"; - } - print "</tr>\n"; - } - print "</table>\n"; + + git_difftree_body(\@difftree, $parent); + git_footer_html(); } sub git_blobdiff { mkdir($git_temp, 0700); git_header_html(); - if (defined $hash_base && (my %co = git_read_commit($hash_base))) { - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; + if (defined $hash_base && (my %co = parse_commit($hash_base))) { + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); } else { print "<div class=\"page_nav\">\n" . "<br/><br/></div>\n" . @@ -2235,40 +2319,30 @@ sub git_blobdiff_plain { sub git_commitdiff { mkdir($git_temp, 0700); - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } if (!defined $hash_parent) { - $hash_parent = $co{'parent'}; + $hash_parent = $co{'parent'} || '--root'; } - open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); - my (@difftree) = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-diff-tree failed"); # non-textual hash id's can be cached my $expires; if ($hash =~ m/^[0-9a-fA-F]{40}$/) { $expires = "+1d"; } - my $refs = read_info_ref(); - my $ref = ""; - if (defined $refs->{$co{'id'}}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$co{'id'}}) . "</span>"; - } + my $refs = git_get_references(); + my $ref = format_ref_marker($refs, $co{'id'}); + my $formats_nav = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain"); git_header_html(undef, $expires); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | commitdiff" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . "<br/>\n"; - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff_plain;h=$hash;hp=$hash_parent")}, "plain") . "\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'}) . $ref) . "\n" . - "</div>\n"; + git_print_page_nav('commitdiff','', $hash,$co{'tree'},$hash, $formats_nav); + git_print_header_div('commit', esc_html($co{'title'}) . $ref, $hash); print "<div class=\"page_body\">\n"; my $comment = $co{'comment'}; my $empty = 0; @@ -2297,7 +2371,9 @@ sub git_commitdiff { foreach my $line (@difftree) { # ':100644 100644 03b218260e99b78c6df0ed378e59ed9205ccc96d 3b93d5e7cc7f7dd4ebed13a5cc1a4ad976fc94d8 M ls-files.c' # ':100644 100644 7f9281985086971d3877aca27704f2aaf9c448ce bc190ebc71bbd923f2b728e505408f5e54bd073a M rev-tree.c' - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + next; + } my $from_mode = $1; my $to_mode = $2; my $from_id = $3; @@ -2305,21 +2381,23 @@ sub git_commitdiff { my $status = $5; my $file = validate_input(unquote($6)); if ($status eq "A") { - print "<div class=\"diff_info\">" . file_type($to_mode) . ":" . + print "<div class=\"diff_info\">" . file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id) . "(new)" . "</div>\n"; git_diff_print(undef, "/dev/null", $to_id, "b/$file"); } elsif ($status eq "D") { print "<div class=\"diff_info\">" . file_type($from_mode) . ":" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . "(deleted)" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . "(deleted)" . "</div>\n"; git_diff_print($from_id, "a/$file", undef, "/dev/null"); } elsif ($status eq "M") { if ($from_id ne $to_id) { print "<div class=\"diff_info\">" . - file_type($from_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash;f=$file")}, $from_id) . + file_type($from_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$from_id;hb=$hash_parent;f=$file")}, $from_id) . " -> " . - file_type($to_mode) . ":" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); + file_type($to_mode) . ":" . + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;h=$to_id;hb=$hash;f=$file")}, $to_id); print "</div>\n"; git_diff_print($from_id, "a/$file", $to_id, "b/$file"); } @@ -2332,15 +2410,23 @@ sub git_commitdiff { sub git_commitdiff_plain { mkdir($git_temp, 0700); - open my $fd, "-|", "$GIT diff-tree -r $hash_parent $hash" or die_error(undef, "Open failed."); - my (@difftree) = map { chomp; $_ } <$fd>; - close $fd or die_error(undef, "Reading diff-tree failed."); + my %co = parse_commit($hash); + if (!%co) { + die_error(undef, "Unknown commit object"); + } + if (!defined $hash_parent) { + $hash_parent = $co{'parent'} || '--root'; + } + open my $fd, "-|", $GIT, "diff-tree", '-r', $hash_parent, $hash + or die_error(undef, "Open git-diff-tree failed"); + my @difftree = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading diff-tree failed"); # try to figure out the next tag after this commit my $tagname; - my $refs = read_info_ref("tags"); - open $fd, "-|", "$GIT rev-list HEAD"; - chomp (my (@commits) = <$fd>); + my $refs = git_get_references("tags"); + open $fd, "-|", $GIT, "rev-list", "HEAD"; + my @commits = map { chomp; $_ } <$fd>; close $fd; foreach my $commit (@commits) { if (defined $refs->{$commit}) { @@ -2352,14 +2438,13 @@ sub git_commitdiff_plain { } print $cgi->header(-type => "text/plain", -charset => 'utf-8', '-content-disposition' => "inline; filename=\"git-$hash.patch\""); - my %co = git_read_commit($hash); - my %ad = date_str($co{'author_epoch'}, $co{'author_tz'}); + my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'}); my $comment = $co{'comment'}; print "From: $co{'author'}\n" . "Date: $ad{'rfc2822'} ($ad{'tz_local'})\n". "Subject: $co{'title'}\n"; if (defined $tagname) { - print "X-Git-Tag: $tagname\n"; + print "X-Git-Tag: $tagname\n"; } print "X-Git-Url: $my_url?p=$project;a=commitdiff;h=$hash\n" . "\n"; @@ -2370,7 +2455,9 @@ sub git_commitdiff_plain { print "---\n\n"; foreach my $line (@difftree) { - $line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/; + if ($line !~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)\t(.*)$/) { + next; + } my $from_id = $3; my $to_id = $4; my $status = $5; @@ -2387,27 +2474,17 @@ sub git_commitdiff_plain { sub git_history { if (!defined $hash_base) { - $hash_base = git_read_head($project); + $hash_base = git_get_head_hash($project); } my $ftype; - my %co = git_read_commit($hash_base); + my %co = parse_commit($hash_base); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } - my $refs = read_info_ref(); + my $refs = git_get_references(); git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash_base")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash_base")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash_base"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; + git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base); + git_print_header_div('commit', esc_html($co{'title'}), $hash_base); if (!defined $hash && defined $file_name) { $hash = git_get_hash_by_path($hash_base, $file_name); } @@ -2417,60 +2494,23 @@ sub git_history { git_print_page_path($file_name, $ftype); open my $fd, "-|", - "$GIT rev-list --full-history $hash_base -- \'$file_name\'"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - while (my $line = <$fd>) { - if ($line =~ m/^([0-9a-fA-F]{40})/){ - my $commit = $1; - my %co = git_read_commit($commit); - if (!%co) { - next; - } - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; - } - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; - } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 15, 3)) . "</i></td>\n" . - "<td>" . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, "<b>" . - esc_html(chop_str($co{'title'}, 50)) . "$ref</b>") . "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blob;hb=$commit;f=$file_name")}, "blob"); - my $blob = git_get_hash_by_path($hash_base, $file_name); - my $blob_parent = git_get_hash_by_path($commit, $file_name); - if (defined $blob && defined $blob_parent && $blob ne $blob_parent) { - print " | " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=blobdiff;h=$blob;hp=$blob_parent;hb=$commit;f=$file_name")}, - "diff to current"); - } - print "</td>\n" . - "</tr>\n"; - } - } - print "</table>\n"; + $GIT, "rev-list", "--full-history", $hash_base, "--", $file_name; + git_history_body($fd, $refs, $hash_base, $ftype); + close $fd; git_footer_html(); } sub git_search { if (!defined $searchtext) { - die_error("", "Text field empty."); + die_error(undef, "Text field empty"); } if (!defined $hash) { - $hash = git_read_head($project); + $hash = git_get_head_hash($project); } - my %co = git_read_commit($hash); + my %co = parse_commit($hash); if (!%co) { - die_error(undef, "Unknown commit object."); + die_error(undef, "Unknown commit object"); } # pickaxe may take all resources of your box and run for several minutes # with every query - so decide by yourself how public you make this feature :) @@ -2487,24 +2527,14 @@ sub git_search { $pickaxe_search = 1; } git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary;h=$hash")}, "summary") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "shortlog") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$co{'tree'};hb=$hash")}, "tree") . - "<br/><br/>\n" . - "</div>\n"; + git_print_page_nav('','', $hash,$co{'tree'},$hash); + git_print_header_div('commit', esc_html($co{'title'}), $hash); - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash"), -class => "title"}, esc_html($co{'title'})) . "\n" . - "</div>\n"; print "<table cellspacing=\"0\">\n"; my $alternate = 0; if ($commit_search) { $/ = "\0"; - open my $fd, "-|", "$GIT rev-list --header --parents $hash" or next; + open my $fd, "-|", $GIT, "rev-list", "--header", "--parents", $hash or next; while (my $commit_text = <$fd>) { if (!grep m/$searchtext/i, $commit_text) { next; @@ -2516,7 +2546,7 @@ sub git_search { next; } my @commit_lines = split "\n", $commit_text; - my %co = git_read_commit(undef, \@commit_lines); + my %co = parse_commit(undef, \@commit_lines); if (!%co) { next; } @@ -2597,7 +2627,7 @@ sub git_search { print "</td>\n" . "</tr>\n"; } - %co = git_read_commit($1); + %co = parse_commit($1); } } close $fd; @@ -2607,90 +2637,130 @@ sub git_search { } sub git_shortlog { - my $head = git_read_head($project); + my $head = git_get_head_hash($project); if (!defined $hash) { $hash = $head; } if (!defined $page) { $page = 0; } - my $refs = read_info_ref(); - git_header_html(); - print "<div class=\"page_nav\">\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary")}, "summary") . - " | shortlog" . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=log;h=$hash")}, "log") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$hash")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$hash")}, "commitdiff") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=tree;h=$hash;hb=$hash")}, "tree") . "<br/>\n"; + my $refs = git_get_references(); my $limit = sprintf("--max-count=%i", (100 * ($page+1))); - open my $fd, "-|", "$GIT rev-list $limit $hash" or die_error(undef, "Open failed."); - my (@revlist) = map { chomp; $_ } <$fd>; + open my $fd, "-|", $GIT, "rev-list", $limit, $hash + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; close $fd; - if ($hash ne $head || $page) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog")}, "HEAD"); - } else { - print "HEAD"; - } - if ($page > 0) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page-1)), -accesskey => "p", -title => "Alt-p"}, "prev"); - } else { - print " ⋅ prev"; - } + my $paging_nav = format_paging_nav('shortlog', $hash, $head, $page, $#revlist); + my $next_link = ''; if ($#revlist >= (100 * ($page+1)-1)) { - print " ⋅ " . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -accesskey => "n", -title => "Alt-n"}, "next"); - } else { - print " ⋅ next"; + $next_link = + $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), + -title => "Alt-n"}, "next"); } - print "<br/>\n" . - "</div>\n"; - print "<div>\n" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=summary"), -class => "title"}, " ") . - "</div>\n"; - print "<table cellspacing=\"0\">\n"; - my $alternate = 0; - for (my $i = ($page * 100); $i <= $#revlist; $i++) { + + + git_header_html(); + git_print_page_nav('shortlog','', $hash,$hash,$hash, $paging_nav); + git_print_header_div('summary', $project); + + git_shortlog_body(\@revlist, ($page * 100), $#revlist, $refs, $next_link); + + git_footer_html(); +} + +## ...................................................................... +## feeds (RSS, OPML) + +sub git_rss { + # http://www.notestips.com/80256B3A007F2692/1/NAMO5P9UPQ + open my $fd, "-|", $GIT, "rev-list", "--max-count=150", git_get_head_hash($project) + or die_error(undef, "Open git-rev-list failed"); + my @revlist = map { chomp; $_ } <$fd>; + close $fd or die_error(undef, "Reading git-rev-list failed"); + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". + "<rss version=\"2.0\" xmlns:content=\"http://purl.org/rss/1.0/modules/content/\">\n"; + print "<channel>\n"; + print "<title>$project</title>\n". + "<link>" . esc_html("$my_url?p=$project;a=summary") . "</link>\n". + "<description>$project log</description>\n". + "<language>en</language>\n"; + + for (my $i = 0; $i <= $#revlist; $i++) { my $commit = $revlist[$i]; - my $ref = ""; - if (defined $refs->{$commit}) { - $ref = " <span class=\"tag\">" . esc_html($refs->{$commit}) . "</span>"; + my %co = parse_commit($commit); + # we read 150, we always show 30 and the ones more recent than 48 hours + if (($i >= 20) && ((time - $co{'committer_epoch'}) > 48*60*60)) { + last; } - my %co = git_read_commit($commit); - my %ad = date_str($co{'author_epoch'}); - if ($alternate) { - print "<tr class=\"dark\">\n"; - } else { - print "<tr class=\"light\">\n"; + my %cd = parse_date($co{'committer_epoch'}); + open $fd, "-|", $GIT, "diff-tree", '-r', $co{'parent'}, $co{'id'} or next; + my @difftree = map { chomp; $_ } <$fd>; + close $fd or next; + print "<item>\n" . + "<title>" . + sprintf("%d %s %02d:%02d", $cd{'mday'}, $cd{'month'}, $cd{'hour'}, $cd{'minute'}) . " - " . esc_html($co{'title'}) . + "</title>\n" . + "<author>" . esc_html($co{'author'}) . "</author>\n" . + "<pubDate>$cd{'rfc2822'}</pubDate>\n" . + "<guid isPermaLink=\"true\">" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</guid>\n" . + "<link>" . esc_html("$my_url?p=$project;a=commit;h=$commit") . "</link>\n" . + "<description>" . esc_html($co{'title'}) . "</description>\n" . + "<content:encoded>" . + "<![CDATA[\n"; + my $comment = $co{'comment'}; + foreach my $line (@$comment) { + $line = decode("utf8", $line, Encode::FB_DEFAULT); + print "$line<br/>\n"; } - $alternate ^= 1; - print "<td title=\"$co{'age_string_age'}\"><i>$co{'age_string_date'}</i></td>\n" . - "<td><i>" . esc_html(chop_str($co{'author_name'}, 10)) . "</i></td>\n" . - "<td>"; - if (length($co{'title_short'}) < length($co{'title'})) { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list", -title => "$co{'title'}"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); - } else { - print $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit"), -class => "list"}, - "<b>" . esc_html($co{'title_short'}) . "$ref</b>"); + print "<br/>\n"; + foreach my $line (@difftree) { + if (!($line =~ m/^:([0-7]{6}) ([0-7]{6}) ([0-9a-fA-F]{40}) ([0-9a-fA-F]{40}) (.)([0-9]{0,3})\t(.*)$/)) { + next; + } + my $file = validate_input(unquote($7)); + $file = decode("utf8", $file, Encode::FB_DEFAULT); + print "$file<br/>\n"; } - print "</td>\n" . - "<td class=\"link\">" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commit;h=$commit")}, "commit") . - " | " . $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=commitdiff;h=$commit")}, "commitdiff") . - "</td>\n" . - "</tr>"; + print "]]>\n" . + "</content:encoded>\n" . + "</item>\n"; } - if ($#revlist >= (100 * ($page+1)-1)) { - print "<tr>\n" . - "<td>" . - $cgi->a({-href => "$my_uri?" . esc_param("p=$project;a=shortlog;h=$hash;pg=" . ($page+1)), -title => "Alt-n"}, "next") . - "</td>\n" . - "</tr>\n"; + print "</channel></rss>"; +} + +sub git_opml { + my @list = git_get_projects_list(); + + print $cgi->header(-type => 'text/xml', -charset => 'utf-8'); + print "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n". + "<opml version=\"1.0\">\n". + "<head>". + " <title>$site_name Git OPML Export</title>\n". + "</head>\n". + "<body>\n". + "<outline text=\"git RSS feeds\">\n"; + + foreach my $pr (@list) { + my %proj = %$pr; + my $head = git_get_head_hash($proj{'path'}); + if (!defined $head) { + next; + } + $ENV{'GIT_DIR'} = "$projectroot/$proj{'path'}"; + my %co = parse_commit($head); + if (!%co) { + next; + } + + my $path = esc_html(chop_str($proj{'path'}, 25, 5)); + my $rss = "$my_url?p=$proj{'path'};a=rss"; + my $html = "$my_url?p=$proj{'path'};a=summary"; + print "<outline type=\"rss\" text=\"$path\" title=\"$path\" xmlUrl=\"$rss\" htmlUrl=\"$html\"/>\n"; } - print "</table\n>"; - git_footer_html(); + print "</outline>\n". + "</body>\n". + "</opml>\n"; } diff --git a/builtin-help.c b/help.c index 6484cb9df2..9ecdefdb03 100644 --- a/builtin-help.c +++ b/help.c @@ -14,7 +14,7 @@ static int term_columns(void) { char *col_string = getenv("COLUMNS"); - int n_cols = 0; + int n_cols; if (col_string && (n_cols = atoi(col_string)) > 0) return n_cols; diff --git a/http-fetch.c b/http-fetch.c index de5fc44e66..d1f74b443b 100644 --- a/http-fetch.c +++ b/http-fetch.c @@ -36,10 +36,10 @@ enum XML_Status { #define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 -static int commits_on_stdin = 0; +static int commits_on_stdin; static int got_alternates = -1; -static int corrupt_object_found = 0; +static int corrupt_object_found; static struct curl_slist *no_pragma_header; @@ -52,7 +52,7 @@ struct alt_base struct alt_base *next; }; -static struct alt_base *alt = NULL; +static struct alt_base *alt; enum object_request_state { WAITING, @@ -114,7 +114,7 @@ struct remote_ls_ctx }; #endif -static struct object_request *object_queue_head = NULL; +static struct object_request *object_queue_head; static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, void *data) @@ -301,7 +301,7 @@ static void finish_object_request(struct object_request *obj_req) unlink(obj_req->tmpfile); return; } - if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { unlink(obj_req->tmpfile); return; } @@ -1070,7 +1070,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) int ret = 0; struct object_request *obj_req = object_queue_head; - while (obj_req != NULL && memcmp(obj_req->sha1, sha1, 20)) + while (obj_req != NULL && hashcmp(obj_req->sha1, sha1)) obj_req = obj_req->next; if (obj_req == NULL) return error("Couldn't find request for %s in the queue", hex); @@ -1109,7 +1109,7 @@ static int fetch_object(struct alt_base *repo, unsigned char *sha1) } else if (obj_req->zret != Z_STREAM_END) { corrupt_object_found++; ret = error("File %s (%s) corrupt", hex, obj_req->url); - } else if (memcmp(obj_req->sha1, obj_req->real_sha1, 20)) { + } else if (hashcmp(obj_req->sha1, obj_req->real_sha1)) { ret = error("File %s has bad hash", hex); } else if (obj_req->rename < 0) { ret = error("unable to write sha1 filename %s", diff --git a/http-push.c b/http-push.c index d45733ef64..48497797f1 100644 --- a/http-push.c +++ b/http-push.c @@ -70,18 +70,18 @@ enum XML_Status { /* We allow "recursive" symbolic refs. Only within reason, though */ #define MAXDEPTH 5 -static int pushing = 0; -static int aborted = 0; +static int pushing; +static int aborted; static signed char remote_dir_exists[256]; static struct curl_slist *no_pragma_header; static struct curl_slist *default_headers; -static int push_verbosely = 0; -static int push_all = 0; -static int force_all = 0; +static int push_verbosely; +static int push_all; +static int force_all; -static struct object_list *objects = NULL; +static struct object_list *objects; struct repo { @@ -94,7 +94,7 @@ struct repo struct remote_lock *locks; }; -static struct repo *remote = NULL; +static struct repo *remote; enum transfer_state { NEED_FETCH, @@ -134,7 +134,7 @@ struct transfer_request struct transfer_request *next; }; -static struct transfer_request *request_queue_head = NULL; +static struct transfer_request *request_queue_head; struct xml_ctx { @@ -745,7 +745,7 @@ static void finish_request(struct transfer_request *request) SHA1_Final(request->real_sha1, &request->c); if (request->zret != Z_STREAM_END) { unlink(request->tmpfile); - } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) { + } else if (hashcmp(request->obj->sha1, request->real_sha1)) { unlink(request->tmpfile); } else { request->rename = @@ -2182,49 +2182,11 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1) static int verify_merge_base(unsigned char *head_sha1, unsigned char *branch_sha1) { - int pipe_fd[2]; - pid_t merge_base_pid; - char line[PATH_MAX + 20]; - unsigned char merge_sha1[20]; - int verified = 0; - - if (pipe(pipe_fd) < 0) - die("Verify merge base: pipe failed"); - - merge_base_pid = fork(); - if (!merge_base_pid) { - static const char *args[] = { - "merge-base", - "-a", - NULL, - NULL, - NULL - }; - args[2] = strdup(sha1_to_hex(head_sha1)); - args[3] = sha1_to_hex(branch_sha1); - - dup2(pipe_fd[1], 1); - close(pipe_fd[0]); - close(pipe_fd[1]); - execv_git_cmd(args); - die("merge-base setup failed"); - } - if (merge_base_pid < 0) - die("merge-base fork failed"); - - dup2(pipe_fd[0], 0); - close(pipe_fd[0]); - close(pipe_fd[1]); - while (fgets(line, sizeof(line), stdin) != NULL) { - if (get_sha1_hex(line, merge_sha1)) - die("expected sha1, got garbage:\n %s", line); - if (!memcmp(branch_sha1, merge_sha1, 20)) { - verified = 1; - break; - } - } + struct commit *head = lookup_commit(head_sha1); + struct commit *branch = lookup_commit(branch_sha1); + struct commit_list *merge_bases = get_merge_bases(head, branch, 1); - return verified; + return (merge_bases && !merge_bases->next && merge_bases->item == branch); } static int delete_remote_branch(char *pattern, int force) @@ -2454,7 +2416,7 @@ int main(int argc, char **argv) if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (push_verbosely || 1) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; diff --git a/index-pack.c b/index-pack.c index b20659c259..96ea687463 100644 --- a/index-pack.c +++ b/index-pack.c @@ -82,7 +82,7 @@ static void parse_pack_header(void) SHA1_Init(&ctx); SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, pack_base + pack_size - 20)) die("packfile '%s' SHA1 mismatch", pack_name); } @@ -189,7 +189,7 @@ static int find_delta(const unsigned char *base_sha1) struct delta_entry *delta = &deltas[next]; int cmp; - cmp = memcmp(base_sha1, delta->base_sha1, 20); + cmp = hashcmp(base_sha1, delta->base_sha1); if (!cmp) return next; if (cmp < 0) { @@ -210,9 +210,9 @@ static int find_deltas_based_on_sha1(const unsigned char *base_sha1, if (first < 0) return -1; - while (first > 0 && !memcmp(deltas[first-1].base_sha1, base_sha1, 20)) + while (first > 0 && !hashcmp(deltas[first - 1].base_sha1, base_sha1)) --first; - while (last < end && !memcmp(deltas[last+1].base_sha1, base_sha1, 20)) + while (last < end && !hashcmp(deltas[last + 1].base_sha1, base_sha1)) ++last; *first_index = first; *last_index = last; @@ -278,7 +278,7 @@ static int compare_delta_entry(const void *a, const void *b) { const struct delta_entry *delta_a = a; const struct delta_entry *delta_b = b; - return memcmp(delta_a->base_sha1, delta_b->base_sha1, 20); + return hashcmp(delta_a->base_sha1, delta_b->base_sha1); } static void parse_pack_objects(void) @@ -350,7 +350,7 @@ static int sha1_compare(const void *_a, const void *_b) { struct object_entry *a = *(struct object_entry **)_a; struct object_entry *b = *(struct object_entry **)_b; - return memcmp(a->sha1, b->sha1, 20); + return hashcmp(a->sha1, b->sha1); } static void write_index_file(const char *index_name, unsigned char *sha1) diff --git a/local-fetch.c b/local-fetch.c index 7d01845d39..7b6875cce6 100644 --- a/local-fetch.c +++ b/local-fetch.c @@ -5,10 +5,10 @@ #include "commit.h" #include "fetch.h" -static int use_link = 0; -static int use_symlink = 0; +static int use_link; +static int use_symlink; static int use_filecopy = 1; -static int commits_on_stdin = 0; +static int commits_on_stdin; static const char *path; /* "Remote" git repository */ @@ -16,7 +16,7 @@ void prefetch(unsigned char *sha1) { } -static struct packed_git *packs = NULL; +static struct packed_git *packs; static void setup_index(unsigned char *sha1) { diff --git a/log-tree.c b/log-tree.c index 05ede0c175..031af88933 100644 --- a/log-tree.c +++ b/log-tree.c @@ -19,7 +19,7 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) char *cp = buf; /* Do we have enough space to add it? */ - if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 2) + if (buf_sz - at <= strlen(signed_off_by) + signoff_len + 3) return at; /* First see if we already have the sign-off by the signer */ @@ -34,6 +34,48 @@ static int append_signoff(char *buf, int buf_sz, int at, const char *signoff) return at; /* we already have him */ } + /* Does the last line already end with "^[-A-Za-z]+: [^@]+@"? + * If not, add a blank line to separate the message from + * the run of Signed-off-by: and Acked-by: lines. + */ + { + char ch; + int seen_colon, seen_at, seen_name, seen_head, not_signoff; + seen_colon = 0; + seen_at = 0; + seen_name = 0; + seen_head = 0; + not_signoff = 0; + cp = buf + at; + while (buf <= --cp && (ch = *cp) == '\n') + ; + while (!not_signoff && buf <= cp && (ch = *cp--) != '\n') { + if (!seen_at) { + if (ch == '@') + seen_at = 1; + continue; + } + if (!seen_colon) { + if (ch == '@') + not_signoff = 1; + else if (ch == ':') + seen_colon = 1; + else + seen_name = 1; + continue; + } + if (('A' <= ch && ch <= 'Z') || + ('a' <= ch && ch <= 'z') || + ch == '-') { + seen_head = 1; + continue; + } + not_signoff = 1; + } + if (not_signoff || !seen_head || !seen_name) + buf[at++] = '\n'; + } + strcpy(buf + at, signed_off_by); at += strlen(signed_off_by); strcpy(buf + at, signoff); diff --git a/merge-base.c b/merge-base.c index 59f723f404..009caf804b 100644 --- a/merge-base.c +++ b/merge-base.c @@ -2,7 +2,7 @@ #include "cache.h" #include "commit.h" -static int show_all = 0; +static int show_all; static int merge_base(struct commit *rev1, struct commit *rev2) { diff --git a/merge-index.c b/merge-index.c index 0498a6f45e..646d090c58 100644 --- a/merge-index.c +++ b/merge-index.c @@ -4,14 +4,15 @@ #include "cache.h" -static const char *pgm = NULL; +static const char *pgm; static const char *arguments[8]; static int one_shot, quiet; static int err; static void run_program(void) { - int pid = fork(), status; + pid_t pid = fork(); + int status; if (pid < 0) die("unable to fork"); diff --git a/merge-tree.c b/merge-tree.c index 7cf00be6d5..c2e9a867ed 100644 --- a/merge-tree.c +++ b/merge-tree.c @@ -152,7 +152,7 @@ static int same_entry(struct name_entry *a, struct name_entry *b) { return a->sha1 && b->sha1 && - !memcmp(a->sha1, b->sha1, 20) && + !hashcmp(a->sha1, b->sha1) && a->mode == b->mode; } @@ -49,7 +49,6 @@ static void write_tree(unsigned char *sha1) int i; qsort(entries, used, sizeof(*entries), ent_compare); - size = 100; for (size = i = 0; i < used; i++) size += 32 + entries[i]->len; buffer = xmalloc(size); @@ -58,7 +58,7 @@ struct object *lookup_object(const unsigned char *sha1) i = hashtable_index(sha1); while ((obj = obj_hash[i]) != NULL) { - if (!memcmp(sha1, obj->sha1, 20)) + if (!hashcmp(sha1, obj->sha1)) break; i++; if (i == obj_hash_size) diff --git a/pack-check.c b/pack-check.c index 3a62e1b7e4..04c6c00821 100644 --- a/pack-check.c +++ b/pack-check.c @@ -29,10 +29,10 @@ static int verify_packfile(struct packed_git *p) pack_base = p->pack_base; SHA1_Update(&ctx, pack_base, pack_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, (char *) pack_base + pack_size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)pack_base + pack_size - 20)) return error("Packfile %s SHA1 mismatch with itself", p->pack_name); - if (memcmp(sha1, (char *) index_base + index_size - 40, 20)) + if (hashcmp(sha1, (unsigned char *)index_base + index_size - 40)) return error("Packfile %s SHA1 mismatch with idx", p->pack_name); @@ -135,7 +135,7 @@ int verify_pack(struct packed_git *p, int verbose) SHA1_Init(&ctx); SHA1_Update(&ctx, index_base, index_size - 20); SHA1_Final(sha1, &ctx); - if (memcmp(sha1, (char *) index_base + index_size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)index_base + index_size - 20)) ret = error("Packfile index for %s SHA1 mismatch", p->pack_name); diff --git a/pack-redundant.c b/pack-redundant.c index 41fb960569..edb5524fc4 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -13,7 +13,7 @@ static const char pack_redundant_usage[] = "git-pack-redundant [ --verbose ] [ --alt-odb ] < --all | <.pack filename> ...>"; -static int load_all_packs = 0, verbose = 0, alt_odb = 0; +static int load_all_packs, verbose, alt_odb; struct llist_item { struct llist_item *next; @@ -37,7 +37,7 @@ struct pll { struct pack_list *pl; }; -static struct llist_item *free_nodes = NULL; +static struct llist_item *free_nodes; static inline void llist_item_put(struct llist_item *item) { @@ -139,7 +139,7 @@ static inline struct llist_item *llist_insert_sorted_unique(struct llist *list, l = (hint == NULL) ? list->front : hint; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) { /* we insert before this entry */ return llist_insert(list, prev, sha1); } @@ -162,7 +162,7 @@ redo_from_start: l = (hint == NULL) ? list->front : hint; prev = NULL; while (l) { - int cmp = memcmp(l->sha1, sha1, 20); + int cmp = hashcmp(l->sha1, sha1); if (cmp > 0) /* not in list, since sorted */ return prev; if(!cmp) { /* found */ @@ -256,7 +256,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) while (p1_off <= p1->pack->index_size - 3 * 20 && p2_off <= p2->pack->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { p1_hint = llist_sorted_remove(p1->unique_objects, @@ -351,16 +351,16 @@ static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; int p1_off, p2_off; - char *p1_base, *p2_base; + unsigned char *p1_base, *p2_base; p1_off = p2_off = 256 * 4 + 4; - p1_base = (char *)p1->index_base; - p2_base = (char *)p2->index_base; + p1_base = (unsigned char *)p1->index_base; + p2_base = (unsigned char *)p2->index_base; while (p1_off <= p1->index_size - 3 * 20 && p2_off <= p2->index_size - 3 * 20) { - int cmp = memcmp(p1_base + p1_off, p2_base + p2_off, 20); + int cmp = hashcmp(p1_base + p1_off, p2_base + p2_off); /* cmp ~ p1 - p2 */ if (cmp == 0) { ret++; @@ -15,11 +15,13 @@ void setup_pager(void) { pid_t pid; int fd[2]; - const char *pager = getenv("PAGER"); + const char *pager = getenv("GIT_PAGER"); if (!isatty(1)) return; if (!pager) + pager = getenv("PAGER"); + if (!pager) pager = "less"; else if (!*pager || !strcmp(pager, "cat")) return; diff --git a/read-cache.c b/read-cache.c index f92cdaacee..b6982eac41 100644 --- a/read-cache.c +++ b/read-cache.c @@ -18,16 +18,16 @@ #define CACHE_EXT(s) ( (s[0]<<24)|(s[1]<<16)|(s[2]<<8)|(s[3]) ) #define CACHE_EXT_TREE 0x54524545 /* "TREE" */ -struct cache_entry **active_cache = NULL; +struct cache_entry **active_cache; static time_t index_file_timestamp; -unsigned int active_nr = 0, active_alloc = 0, active_cache_changed = 0; +unsigned int active_nr, active_alloc, active_cache_changed; -struct cache_tree *active_cache_tree = NULL; +struct cache_tree *active_cache_tree; -int cache_errno = 0; +int cache_errno; -static void *cache_mmap = NULL; -static size_t cache_mmap_size = 0; +static void *cache_mmap; +static size_t cache_mmap_size; /* * This only updates the "non-critical" parts of the directory @@ -60,7 +60,7 @@ static int ce_compare_data(struct cache_entry *ce, struct stat *st) if (fd >= 0) { unsigned char sha1[20]; if (!index_fd(sha1, fd, st, 0, NULL)) - match = memcmp(sha1, ce->sha1, 20); + match = hashcmp(sha1, ce->sha1); /* index_fd() closed the file descriptor already */ } return match; @@ -169,9 +169,11 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st) return changed; } -int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) +int ce_match_stat(struct cache_entry *ce, struct stat *st, int options) { unsigned int changed; + int ignore_valid = options & 01; + int assume_racy_is_modified = options & 02; /* * If it's marked as always valid in the index, it's @@ -200,8 +202,12 @@ int ce_match_stat(struct cache_entry *ce, struct stat *st, int ignore_valid) */ if (!changed && index_file_timestamp && - index_file_timestamp <= ntohl(ce->ce_mtime.sec)) - changed |= ce_modified_check_fs(ce, st); + index_file_timestamp <= ntohl(ce->ce_mtime.sec)) { + if (assume_racy_is_modified) + changed |= DATA_CHANGED; + else + changed |= ce_modified_check_fs(ce, st); + } return changed; } @@ -738,7 +744,7 @@ static int verify_hdr(struct cache_header *hdr, unsigned long size) SHA1_Init(&c); SHA1_Update(&c, hdr, size - 20); SHA1_Final(sha1, &c); - if (memcmp(sha1, (char *) hdr + size - 20, 20)) + if (hashcmp(sha1, (unsigned char *)hdr + size - 20)) return error("bad index file sha1 signature"); return 0; } @@ -840,6 +846,18 @@ unmap: static unsigned char write_buffer[WRITE_BUFFER_SIZE]; static unsigned long write_buffer_len; +static int ce_write_flush(SHA_CTX *context, int fd) +{ + unsigned int buffered = write_buffer_len; + if (buffered) { + SHA1_Update(context, write_buffer, buffered); + if (write(fd, write_buffer, buffered) != buffered) + return -1; + write_buffer_len = 0; + } + return 0; +} + static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) { while (len) { @@ -850,8 +868,8 @@ static int ce_write(SHA_CTX *context, int fd, void *data, unsigned int len) memcpy(write_buffer + buffered, data, partial); buffered += partial; if (buffered == WRITE_BUFFER_SIZE) { - SHA1_Update(context, write_buffer, WRITE_BUFFER_SIZE); - if (write(fd, write_buffer, WRITE_BUFFER_SIZE) != WRITE_BUFFER_SIZE) + write_buffer_len = buffered; + if (ce_write_flush(context, fd)) return -1; buffered = 0; } @@ -867,10 +885,8 @@ static int write_index_ext_header(SHA_CTX *context, int fd, { ext = htonl(ext); sz = htonl(sz); - if ((ce_write(context, fd, &ext, 4) < 0) || - (ce_write(context, fd, &sz, 4) < 0)) - return -1; - return 0; + return ((ce_write(context, fd, &ext, 4) < 0) || + (ce_write(context, fd, &sz, 4) < 0)) ? -1 : 0; } static int ce_flush(SHA_CTX *context, int fd) @@ -892,9 +908,7 @@ static int ce_flush(SHA_CTX *context, int fd) /* Append the SHA1 signature at the end */ SHA1_Final(write_buffer + left, context); left += 20; - if (write(fd, write_buffer, left) != left) - return -1; - return 0; + return (write(fd, write_buffer, left) != left) ? -1 : 0; } static void ce_smudge_racily_clean_entry(struct cache_entry *ce) @@ -923,7 +937,7 @@ static void ce_smudge_racily_clean_entry(struct cache_entry *ce) * $ echo filfre >nitfol * $ git-update-index --add nitfol * - * but it does not. Whe the second update-index runs, + * but it does not. When the second update-index runs, * it notices that the entry "frotz" has the same timestamp * as index, and if we were to smudge it by resetting its * size to zero here, then the object name recorded diff --git a/receive-pack.c b/receive-pack.c index 93929b5371..81e91909b8 100644 --- a/receive-pack.c +++ b/receive-pack.c @@ -8,10 +8,10 @@ static const char receive_pack_usage[] = "git-receive-pack <git-dir>"; static const char *unpacker[] = { "unpack-objects", NULL }; -static int report_status = 0; +static int report_status; static char capabilities[] = "report-status"; -static int capabilities_sent = 0; +static int capabilities_sent; static int show_ref(const char *path, const unsigned char *sha1) { @@ -40,7 +40,7 @@ struct command { char ref_name[FLEX_ARRAY]; /* more */ }; -static struct command *commands = NULL; +static struct command *commands; static int is_all_zeroes(const char *hex) { @@ -281,7 +281,7 @@ static struct ref_lock *verify_lock(struct ref_lock *lock, unlock_ref(lock); return NULL; } - if (memcmp(lock->old_sha1, old_sha1, 20)) { + if (hashcmp(lock->old_sha1, old_sha1)) { error("Ref %s is at %s but expected %s", lock->ref_file, sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1)); unlock_ref(lock); @@ -411,7 +411,7 @@ int write_ref_sha1(struct ref_lock *lock, if (!lock) return -1; - if (!lock->force_write && !memcmp(lock->old_sha1, sha1, 20)) { + if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) { unlock_ref(lock); return 0; } @@ -475,7 +475,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) die("Log %s is corrupt.", logfile); if (get_sha1_hex(rec + 41, sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s has gap after %s.\n", @@ -489,7 +489,7 @@ int read_ref_at(const char *ref, unsigned long at_time, unsigned char *sha1) else { if (get_sha1_hex(rec + 41, logged_sha1)) die("Log %s is corrupt.", logfile); - if (memcmp(logged_sha1, sha1, 20)) { + if (hashcmp(logged_sha1, sha1)) { tz = strtoul(tz_c, NULL, 10); fprintf(stderr, "warning: Log %s unexpectedly ended on %s.\n", diff --git a/run-command.c b/run-command.c index ca67ee9333..61908682b9 100644 --- a/run-command.c +++ b/run-command.c @@ -25,15 +25,15 @@ int run_command_v_opt(int argc, const char **argv, int flags) } for (;;) { int status, code; - int retval = waitpid(pid, &status, 0); + pid_t waiting = waitpid(pid, &status, 0); - if (retval < 0) { + if (waiting < 0) { if (errno == EINTR) continue; - error("waitpid failed (%s)", strerror(retval)); + error("waitpid failed (%s)", strerror(errno)); return -ERR_RUN_COMMAND_WAITPID; } - if (retval != pid) + if (waiting != pid) return -ERR_RUN_COMMAND_WAITPID_WRONG_PID; if (WIFSIGNALED(status)) return -ERR_RUN_COMMAND_WAITPID_SIGNAL; diff --git a/send-pack.c b/send-pack.c index 10bc8bc359..f7c0cfc6f6 100644 --- a/send-pack.c +++ b/send-pack.c @@ -9,10 +9,10 @@ static const char send_pack_usage[] = "git-send-pack [--all] [--exec=git-receive-pack] <remote> [<head>...]\n" " --all and explicit <head> specification are mutually exclusive."; static const char *exec = "git-receive-pack"; -static int verbose = 0; -static int send_all = 0; -static int force_update = 0; -static int use_thin_pack = 0; +static int verbose; +static int send_all; +static int force_update; +static int use_thin_pack; static int is_zero_sha1(const unsigned char *sha1) { @@ -111,7 +111,7 @@ static void rev_list(int fd, struct ref *refs) exec_rev_list(refs); } -static int pack_objects(int fd, struct ref *refs) +static void pack_objects(int fd, struct ref *refs) { pid_t rev_list_pid; @@ -126,7 +126,6 @@ static int pack_objects(int fd, struct ref *refs) * We don't wait for the rev-list pipeline in the parent: * we end up waiting for the other end instead */ - return 0; } static void unmark_and_free(struct commit_list *list, unsigned int mark) @@ -266,7 +265,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) char old_hex[60], *new_hex; if (!ref->peer_ref) continue; - if (!memcmp(ref->old_sha1, ref->peer_ref->new_sha1, 20)) { + if (!hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); continue; diff --git a/sha1_file.c b/sha1_file.c index 3db956dd5c..04f7f94d21 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -22,7 +22,7 @@ #endif #endif -const unsigned char null_sha1[20] = { 0, }; +const unsigned char null_sha1[20]; static unsigned int sha1_file_open_flag = O_NOATIME; @@ -485,10 +485,10 @@ int use_packed_git(struct packed_git *p) /* Check if the pack file matches with the index file. * this is cheap. */ - if (memcmp((char*)(p->index_base) + p->index_size - 40, - (char *) p->pack_base + p->pack_size - 20, - 20)) { - + if (hashcmp((unsigned char *)(p->index_base) + + p->index_size - 40, + (unsigned char *)p->pack_base + + p->pack_size - 20)) { die("packfile %s does not match index.", p->pack_name); } } @@ -643,11 +643,10 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size)); SHA1_Update(&c, map, size); SHA1_Final(real_sha1, &c); - return memcmp(sha1, real_sha1, 20) ? -1 : 0; + return hashcmp(sha1, real_sha1) ? -1 : 0; } -static void *map_sha1_file_internal(const unsigned char *sha1, - unsigned long *size) +void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { struct stat st; void *map; @@ -684,10 +683,26 @@ static void *map_sha1_file_internal(const unsigned char *sha1, return map; } +int legacy_loose_object(unsigned char *map) +{ + unsigned int word; + + /* + * Is it a zlib-compressed buffer? If so, the first byte + * must be 0x78 (15-bit window size, deflated), and the + * first 16-bit word is evenly divisible by 31 + */ + word = (map[0] << 8) + map[1]; + if (map[0] == 0x78 && !(word % 31)) + return 1; + else + return 0; +} + static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz) { unsigned char c; - unsigned int word, bits; + unsigned int bits; unsigned long size; static const char *typename[8] = { NULL, /* OBJ_EXT */ @@ -703,13 +718,7 @@ static int unpack_sha1_header(z_stream *stream, unsigned char *map, unsigned lon stream->next_out = buffer; stream->avail_out = bufsiz; - /* - * Is it a zlib-compressed buffer? If so, the first byte - * must be 0x78 (15-bit window size, deflated), and the - * first 16-bit word is evenly divisible by 31 - */ - word = (map[0] << 8) + map[1]; - if (map[0] == 0x78 && !(word % 31)) { + if (legacy_loose_object(map)) { inflateInit(stream); return inflate(stream, 0); } @@ -932,7 +941,7 @@ int check_reuse_pack_delta(struct packed_git *p, unsigned long offset, ptr = unpack_object_header(p, ptr, kindp, sizep); if (*kindp != OBJ_DELTA) goto done; - memcpy(base, (char *) p->pack_base + ptr, 20); + memcpy(base, (unsigned char *) p->pack_base + ptr, 20); status = 0; done: unuse_packed_git(p); @@ -1197,7 +1206,7 @@ int find_pack_entry_one(const unsigned char *sha1, do { int mi = (lo + hi) / 2; - int cmp = memcmp((char *) index + (24 * mi) + 4, sha1, 20); + int cmp = hashcmp((unsigned char *)index + (24 * mi) + 4, sha1); if (!cmp) { e->offset = ntohl(*((unsigned int *) ((char *) index + (24 * mi)))); memcpy(e->sha1, sha1, 20); @@ -1246,7 +1255,7 @@ int sha1_object_info(const unsigned char *sha1, char *type, unsigned long *sizep z_stream stream; char hdr[128]; - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (!map) { struct pack_entry e; @@ -1291,7 +1300,7 @@ void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size if (find_pack_entry(sha1, &e)) return read_packed_sha1(sha1, type, size); - map = map_sha1_file_internal(sha1, &mapsize); + map = map_sha1_file(sha1, &mapsize); if (map) { buf = unpack_sha1_file(map, mapsize, type, size); munmap(map, mapsize); @@ -1629,7 +1638,7 @@ int write_sha1_to_fd(int fd, const unsigned char *sha1) { int retval; unsigned long objsize; - void *buf = map_sha1_file_internal(sha1, &objsize); + void *buf = map_sha1_file(sha1, &objsize); if (buf) { retval = write_buffer(fd, buf, objsize); @@ -1706,7 +1715,7 @@ int write_sha1_from_fd(const unsigned char *sha1, int fd, char *buffer, unlink(tmpfile); return error("File %s corrupted", sha1_to_hex(sha1)); } - if (memcmp(sha1, real_sha1, 20)) { + if (hashcmp(sha1, real_sha1)) { unlink(tmpfile); return error("File %s has bad hash", sha1_to_hex(sha1)); } diff --git a/sha1_name.c b/sha1_name.c index c5a05faeb6..e9eb6ce882 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -84,7 +84,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne int cmp; nth_packed_object_sha1(p, mid, now); - cmp = memcmp(match, now, 20); + cmp = hashcmp(match, now); if (!cmp) { first = mid; break; @@ -106,7 +106,7 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne memcpy(found_sha1, now, 20); found++; } - else if (memcmp(found_sha1, now, 20)) { + else if (hashcmp(found_sha1, now)) { found = 2; break; } @@ -144,7 +144,7 @@ static int find_unique_short_object(int len, char *canonical, return 0; } /* Both have unique ones -- do they match? */ - if (memcmp(packed_sha1, unpacked_sha1, 20)) + if (hashcmp(packed_sha1, unpacked_sha1)) return SHORT_NAME_AMBIGUOUS; memcpy(sha1, packed_sha1, 20); return 0; @@ -191,7 +191,7 @@ const char *find_unique_abbrev(const unsigned char *sha1, int len) int status, is_null; static char hex[41]; - is_null = !memcmp(sha1, null_sha1, 20); + is_null = is_null_sha1(sha1); memcpy(hex, sha1_to_hex(sha1), 40); if (len == 40 || !len) return hex; diff --git a/ssh-fetch.c b/ssh-fetch.c index c7d8fa80e4..b006c5c980 100644 --- a/ssh-fetch.c +++ b/ssh-fetch.c @@ -17,7 +17,7 @@ static int fd_in; static int fd_out; -static unsigned char remote_version = 0; +static unsigned char remote_version; static unsigned char local_version = 1; static ssize_t force_write(int fd, void *buffer, size_t length) @@ -36,9 +36,9 @@ static ssize_t force_write(int fd, void *buffer, size_t length) return ret; } -static int prefetches = 0; +static int prefetches; -static struct object_list *in_transit = NULL; +static struct object_list *in_transit; static struct object_list **end_of_transit = &in_transit; void prefetch(unsigned char *sha1) @@ -59,7 +59,7 @@ void prefetch(unsigned char *sha1) } static char conn_buf[4096]; -static size_t conn_buf_posn = 0; +static size_t conn_buf_posn; int fetch(unsigned char *sha1) { @@ -67,7 +67,7 @@ int fetch(unsigned char *sha1) signed char remote; struct object_list *temp; - if (memcmp(sha1, in_transit->item->sha1, 20)) { + if (hashcmp(sha1, in_transit->item->sha1)) { /* we must have already fetched it to clean the queue */ return has_sha1_file(sha1) ? 0 : -1; } diff --git a/ssh-upload.c b/ssh-upload.c index 2da66618fc..20b15eab57 100644 --- a/ssh-upload.c +++ b/ssh-upload.c @@ -15,9 +15,9 @@ #include <string.h> static unsigned char local_version = 1; -static unsigned char remote_version = 0; +static unsigned char remote_version; -static int verbose = 0; +static int verbose; static int serve_object(int fd_in, int fd_out) { ssize_t size; diff --git a/t/t4116-apply-reverse.sh b/t/t4116-apply-reverse.sh new file mode 100755 index 0000000000..69aebe6005 --- /dev/null +++ b/t/t4116-apply-reverse.sh @@ -0,0 +1,46 @@ +#!/bin/sh +# +# Copyright (c) 2005 Junio C Hamano +# + +test_description='git-apply in reverse + +' + +. ./test-lib.sh + +test_expect_success setup ' + + for i in a b c d e f g h i j k l m n; do echo $i; done >file1 && + tr "[ijk]" '\''[\0\1\2]'\'' <file1 >file2 && + + git add file1 file2 && + git commit -m initial && + git tag initial && + + for i in a b c g h i J K L m o n p q; do echo $i; done >file1 && + tr "[mon]" '\''[\0\1\2]'\'' <file1 >file2 && + + git commit -a -m second && + + git diff --binary -R initial >patch + +' + +test_expect_success 'apply in forward' ' + + git apply --index --binary patch && + git diff initial >diff && + diff -u /dev/null diff + +' + +test_expect_success 'apply in reverse' ' + + git apply --reverse --binary --index patch && + git diff >diff && + diff -u /dev/null diff + +' + +test_done diff --git a/t/t7002-grep.sh b/t/t7002-grep.sh index 00a7d762ce..6bfb899ed1 100755 --- a/t/t7002-grep.sh +++ b/t/t7002-grep.sh @@ -3,7 +3,7 @@ # Copyright (c) 2006 Junio C Hamano # -test_description='git grep -w +test_description='git grep various. ' . ./test-lib.sh @@ -19,7 +19,9 @@ test_expect_success setup ' echo x x xx x >x && echo y yy >y && echo zzz > z && - git add file x y z && + mkdir t && + echo test >t/t && + git add file x y z t/t && git commit -m initial ' @@ -80,6 +82,31 @@ do diff expected actual fi ' + + test_expect_success "grep $L (t-1)" ' + echo "${HC}t/t:1:test" >expected && + git grep -n -e test $H >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-2)" ' + echo "${HC}t:1:test" >expected && + ( + cd t && + git grep -n -e test $H + ) >actual && + diff expected actual + ' + + test_expect_success "grep $L (t-3)" ' + echo "${HC}t/t:1:test" >expected && + ( + cd t && + git grep --full-name -n -e test $H + ) >actual && + diff expected actual + ' + done test_done diff --git a/tree-diff.c b/tree-diff.c index 1cdf8aa908..7e2f4f088a 100644 --- a/tree-diff.c +++ b/tree-diff.c @@ -15,7 +15,8 @@ static char *malloc_base(const char *base, const char *path, int pathlen) return newbase; } -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base); +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base); static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) { @@ -38,8 +39,7 @@ static int compare_tree_entry(struct tree_desc *t1, struct tree_desc *t2, const show_entry(opt, "+", t2, base); return 1; } - if (!opt->find_copies_harder && - !memcmp(sha1, sha2, 20) && mode1 == mode2) + if (!opt->find_copies_harder && !hashcmp(sha1, sha2) && mode1 == mode2) return 0; /* @@ -131,7 +131,8 @@ static void show_tree(struct diff_options *opt, const char *prefix, struct tree_ } /* A file entry went away or appeared */ -static int show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, const char *base) +static void show_entry(struct diff_options *opt, const char *prefix, struct tree_desc *desc, + const char *base) { unsigned mode; const char *path; @@ -152,11 +153,9 @@ static int show_entry(struct diff_options *opt, const char *prefix, struct tree_ free(tree); free(newbase); - return 0; + } else { + opt->add_remove(opt, prefix[0], mode, sha1, base, path); } - - opt->add_remove(opt, prefix[0], mode, sha1, base, path); - return 0; } int diff_tree(struct tree_desc *t1, struct tree_desc *t2, const char *base, struct diff_options *opt) @@ -144,7 +144,7 @@ struct tree *lookup_tree(const unsigned char *sha1) return (struct tree *) obj; } -static int track_tree_refs(struct tree *item) +static void track_tree_refs(struct tree *item) { int n_refs = 0, i; struct object_refs *refs; @@ -174,7 +174,6 @@ static int track_tree_refs(struct tree *item) refs->ref[i++] = obj; } set_object_refs(&item->object, refs); - return 0; } int parse_tree_buffer(struct tree *item, void *buffer, unsigned long size) diff --git a/unpack-trees.c b/unpack-trees.c new file mode 100644 index 0000000000..467d9940f5 --- /dev/null +++ b/unpack-trees.c @@ -0,0 +1,799 @@ +#include <signal.h> +#include <sys/time.h> +#include "cache.h" +#include "tree.h" +#include "tree-walk.h" +#include "cache-tree.h" +#include "unpack-trees.h" + +#define DBRT_DEBUG 1 + +struct tree_entry_list { + struct tree_entry_list *next; + unsigned directory : 1; + unsigned executable : 1; + unsigned symlink : 1; + unsigned int mode; + const char *name; + const unsigned char *sha1; +}; + +static struct tree_entry_list *create_tree_entry_list(struct tree *tree) +{ + struct tree_desc desc; + struct name_entry one; + struct tree_entry_list *ret = NULL; + struct tree_entry_list **list_p = &ret; + + if (!tree->object.parsed) + parse_tree(tree); + + desc.buf = tree->buffer; + desc.size = tree->size; + + while (tree_entry(&desc, &one)) { + struct tree_entry_list *entry; + + entry = xmalloc(sizeof(struct tree_entry_list)); + entry->name = one.path; + entry->sha1 = one.sha1; + entry->mode = one.mode; + entry->directory = S_ISDIR(one.mode) != 0; + entry->executable = (one.mode & S_IXUSR) != 0; + entry->symlink = S_ISLNK(one.mode) != 0; + entry->next = NULL; + + *list_p = entry; + list_p = &entry->next; + } + return ret; +} + +static int entcmp(const char *name1, int dir1, const char *name2, int dir2) +{ + int len1 = strlen(name1); + int len2 = strlen(name2); + int len = len1 < len2 ? len1 : len2; + int ret = memcmp(name1, name2, len); + unsigned char c1, c2; + if (ret) + return ret; + c1 = name1[len]; + c2 = name2[len]; + if (!c1 && dir1) + c1 = '/'; + if (!c2 && dir2) + c2 = '/'; + ret = (c1 < c2) ? -1 : (c1 > c2) ? 1 : 0; + if (c1 && c2 && !ret) + ret = len1 - len2; + return ret; +} + +static int unpack_trees_rec(struct tree_entry_list **posns, int len, + const char *base, struct unpack_trees_options *o, + int *indpos, + struct tree_entry_list *df_conflict_list) +{ + int baselen = strlen(base); + int src_size = len + 1; + do { + int i; + const char *first; + int firstdir = 0; + int pathlen; + unsigned ce_size; + struct tree_entry_list **subposns; + struct cache_entry **src; + int any_files = 0; + int any_dirs = 0; + char *cache_name; + int ce_stage; + + /* Find the first name in the input. */ + + first = NULL; + cache_name = NULL; + + /* Check the cache */ + if (o->merge && *indpos < active_nr) { + /* This is a bit tricky: */ + /* If the index has a subdirectory (with + * contents) as the first name, it'll get a + * filename like "foo/bar". But that's after + * "foo", so the entry in trees will get + * handled first, at which point we'll go into + * "foo", and deal with "bar" from the index, + * because the base will be "foo/". The only + * way we can actually have "foo/bar" first of + * all the things is if the trees don't + * contain "foo" at all, in which case we'll + * handle "foo/bar" without going into the + * directory, but that's fine (and will return + * an error anyway, with the added unknown + * file case. + */ + + cache_name = active_cache[*indpos]->name; + if (strlen(cache_name) > baselen && + !memcmp(cache_name, base, baselen)) { + cache_name += baselen; + first = cache_name; + } else { + cache_name = NULL; + } + } + +#if DBRT_DEBUG > 1 + if (first) + printf("index %s\n", first); +#endif + for (i = 0; i < len; i++) { + if (!posns[i] || posns[i] == df_conflict_list) + continue; +#if DBRT_DEBUG > 1 + printf("%d %s\n", i + 1, posns[i]->name); +#endif + if (!first || entcmp(first, firstdir, + posns[i]->name, + posns[i]->directory) > 0) { + first = posns[i]->name; + firstdir = posns[i]->directory; + } + } + /* No name means we're done */ + if (!first) + return 0; + + pathlen = strlen(first); + ce_size = cache_entry_size(baselen + pathlen); + + src = xcalloc(src_size, sizeof(struct cache_entry *)); + + subposns = xcalloc(len, sizeof(struct tree_list_entry *)); + + if (cache_name && !strcmp(cache_name, first)) { + any_files = 1; + src[0] = active_cache[*indpos]; + remove_cache_entry_at(*indpos); + } + + for (i = 0; i < len; i++) { + struct cache_entry *ce; + + if (!posns[i] || + (posns[i] != df_conflict_list && + strcmp(first, posns[i]->name))) { + continue; + } + + if (posns[i] == df_conflict_list) { + src[i + o->merge] = o->df_conflict_entry; + continue; + } + + if (posns[i]->directory) { + struct tree *tree = lookup_tree(posns[i]->sha1); + any_dirs = 1; + parse_tree(tree); + subposns[i] = create_tree_entry_list(tree); + posns[i] = posns[i]->next; + src[i + o->merge] = o->df_conflict_entry; + continue; + } + + if (!o->merge) + ce_stage = 0; + else if (i + 1 < o->head_idx) + ce_stage = 1; + else if (i + 1 > o->head_idx) + ce_stage = 3; + else + ce_stage = 2; + + ce = xcalloc(1, ce_size); + ce->ce_mode = create_ce_mode(posns[i]->mode); + ce->ce_flags = create_ce_flags(baselen + pathlen, + ce_stage); + memcpy(ce->name, base, baselen); + memcpy(ce->name + baselen, first, pathlen + 1); + + any_files = 1; + + memcpy(ce->sha1, posns[i]->sha1, 20); + src[i + o->merge] = ce; + subposns[i] = df_conflict_list; + posns[i] = posns[i]->next; + } + if (any_files) { + if (o->merge) { + int ret; + +#if DBRT_DEBUG > 1 + printf("%s:\n", first); + for (i = 0; i < src_size; i++) { + printf(" %d ", i); + if (src[i]) + printf("%s\n", sha1_to_hex(src[i]->sha1)); + else + printf("\n"); + } +#endif + ret = o->fn(src, o); + +#if DBRT_DEBUG > 1 + printf("Added %d entries\n", ret); +#endif + *indpos += ret; + } else { + for (i = 0; i < src_size; i++) { + if (src[i]) { + add_cache_entry(src[i], ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); + } + } + } + } + if (any_dirs) { + char *newbase = xmalloc(baselen + 2 + pathlen); + memcpy(newbase, base, baselen); + memcpy(newbase + baselen, first, pathlen); + newbase[baselen + pathlen] = '/'; + newbase[baselen + pathlen + 1] = '\0'; + if (unpack_trees_rec(subposns, len, newbase, o, + indpos, df_conflict_list)) + return -1; + free(newbase); + } + free(subposns); + free(src); + } while (1); +} + +/* Unlink the last component and attempt to remove leading + * directories, in case this unlink is the removal of the + * last entry in the directory -- empty directories are removed. + */ +static void unlink_entry(char *name) +{ + char *cp, *prev; + + if (unlink(name)) + return; + prev = NULL; + while (1) { + int status; + cp = strrchr(name, '/'); + if (prev) + *prev = '/'; + if (!cp) + break; + + *cp = 0; + status = rmdir(name); + if (status) { + *cp = '/'; + break; + } + prev = cp; + } +} + +static volatile sig_atomic_t progress_update; + +static void progress_interval(int signum) +{ + progress_update = 1; +} + +static void setup_progress_signal(void) +{ + struct sigaction sa; + struct itimerval v; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = progress_interval; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGALRM, &sa, NULL); + + v.it_interval.tv_sec = 1; + v.it_interval.tv_usec = 0; + v.it_value = v.it_interval; + setitimer(ITIMER_REAL, &v, NULL); +} + +static struct checkout state; +static void check_updates(struct cache_entry **src, int nr, + struct unpack_trees_options *o) +{ + unsigned short mask = htons(CE_UPDATE); + unsigned last_percent = 200, cnt = 0, total = 0; + + if (o->update && o->verbose_update) { + for (total = cnt = 0; cnt < nr; cnt++) { + struct cache_entry *ce = src[cnt]; + if (!ce->ce_mode || ce->ce_flags & mask) + total++; + } + + /* Don't bother doing this for very small updates */ + if (total < 250) + total = 0; + + if (total) { + fprintf(stderr, "Checking files out...\n"); + setup_progress_signal(); + progress_update = 1; + } + cnt = 0; + } + + while (nr--) { + struct cache_entry *ce = *src++; + + if (total) { + if (!ce->ce_mode || ce->ce_flags & mask) { + unsigned percent; + cnt++; + percent = (cnt * 100) / total; + if (percent != last_percent || + progress_update) { + fprintf(stderr, "%4u%% (%u/%u) done\r", + percent, cnt, total); + last_percent = percent; + progress_update = 0; + } + } + } + if (!ce->ce_mode) { + if (o->update) + unlink_entry(ce->name); + continue; + } + if (ce->ce_flags & mask) { + ce->ce_flags &= ~mask; + if (o->update) + checkout_entry(ce, &state, NULL); + } + } + if (total) { + signal(SIGALRM, SIG_IGN); + fputc('\n', stderr); + } +} + +int unpack_trees(struct object_list *trees, struct unpack_trees_options *o) +{ + int indpos = 0; + unsigned len = object_list_length(trees); + struct tree_entry_list **posns; + int i; + struct object_list *posn = trees; + struct tree_entry_list df_conflict_list; + struct cache_entry df_conflict_entry; + + memset(&df_conflict_list, 0, sizeof(df_conflict_list)); + df_conflict_list.next = &df_conflict_list; + memset(&state, 0, sizeof(state)); + state.base_dir = ""; + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + + o->merge_size = len; + memset(&df_conflict_entry, 0, sizeof(df_conflict_entry)); + o->df_conflict_entry = &df_conflict_entry; + + if (len) { + posns = xmalloc(len * sizeof(struct tree_entry_list *)); + for (i = 0; i < len; i++) { + posns[i] = create_tree_entry_list((struct tree *) posn->item); + posn = posn->next; + } + if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "", + o, &indpos, &df_conflict_list)) + return -1; + } + + if (o->trivial_merges_only && o->nontrivial_merge) + die("Merge requires file-level merging"); + + check_updates(active_cache, active_nr, o); + return 0; +} + +/* Here come the merge functions */ + +static void reject_merge(struct cache_entry *ce) +{ + die("Entry '%s' would be overwritten by merge. Cannot merge.", + ce->name); +} + +static int same(struct cache_entry *a, struct cache_entry *b) +{ + if (!!a != !!b) + return 0; + if (!a && !b) + return 1; + return a->ce_mode == b->ce_mode && + !hashcmp(a->sha1, b->sha1); +} + + +/* + * When a CE gets turned into an unmerged entry, we + * want it to be up-to-date + */ +static void verify_uptodate(struct cache_entry *ce, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || o->reset) + return; + + if (!lstat(ce->name, &st)) { + unsigned changed = ce_match_stat(ce, &st, 1); + if (!changed) + return; + errno = 0; + } + if (o->reset) { + ce->ce_flags |= htons(CE_UPDATE); + return; + } + if (errno == ENOENT) + return; + die("Entry '%s' not uptodate. Cannot merge.", ce->name); +} + +static void invalidate_ce_path(struct cache_entry *ce) +{ + if (ce) + cache_tree_invalidate_path(active_cache_tree, ce->name); +} + +/* + * We do not want to remove or overwrite a working tree file that + * is not tracked. + */ +static void verify_absent(const char *path, const char *action, + struct unpack_trees_options *o) +{ + struct stat st; + + if (o->index_only || o->reset || !o->update) + return; + if (!lstat(path, &st)) + die("Untracked working tree file '%s' " + "would be %s by merge.", path, action); +} + +static int merged_entry(struct cache_entry *merge, struct cache_entry *old, + struct unpack_trees_options *o) +{ + merge->ce_flags |= htons(CE_UPDATE); + if (old) { + /* + * See if we can re-use the old CE directly? + * That way we get the uptodate stat info. + * + * This also removes the UPDATE flag on + * a match. + */ + if (same(old, merge)) { + *merge = *old; + } else { + verify_uptodate(old, o); + invalidate_ce_path(old); + } + } + else { + verify_absent(merge->name, "overwritten", o); + invalidate_ce_path(merge); + } + + merge->ce_flags &= ~htons(CE_STAGEMASK); + add_cache_entry(merge, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + return 1; +} + +static int deleted_entry(struct cache_entry *ce, struct cache_entry *old, + struct unpack_trees_options *o) +{ + if (old) + verify_uptodate(old, o); + else + verify_absent(ce->name, "removed", o); + ce->ce_mode = 0; + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE); + invalidate_ce_path(ce); + return 1; +} + +static int keep_entry(struct cache_entry *ce) +{ + add_cache_entry(ce, ADD_CACHE_OK_TO_ADD); + return 1; +} + +#if DBRT_DEBUG +static void show_stage_entry(FILE *o, + const char *label, const struct cache_entry *ce) +{ + if (!ce) + fprintf(o, "%s (missing)\n", label); + else + fprintf(o, "%s%06o %s %d\t%s\n", + label, + ntohl(ce->ce_mode), + sha1_to_hex(ce->sha1), + ce_stage(ce), + ce->name); +} +#endif + +int threeway_merge(struct cache_entry **stages, + struct unpack_trees_options *o) +{ + struct cache_entry *index; + struct cache_entry *head; + struct cache_entry *remote = stages[o->head_idx + 1]; + int count; + int head_match = 0; + int remote_match = 0; + const char *path = NULL; + + int df_conflict_head = 0; + int df_conflict_remote = 0; + + int any_anc_missing = 0; + int no_anc_exists = 1; + int i; + + for (i = 1; i < o->head_idx; i++) { + if (!stages[i]) + any_anc_missing = 1; + else { + if (!path) + path = stages[i]->name; + no_anc_exists = 0; + } + } + + index = stages[0]; + head = stages[o->head_idx]; + + if (head == o->df_conflict_entry) { + df_conflict_head = 1; + head = NULL; + } + + if (remote == o->df_conflict_entry) { + df_conflict_remote = 1; + remote = NULL; + } + + if (!path && index) + path = index->name; + if (!path && head) + path = head->name; + if (!path && remote) + path = remote->name; + + /* First, if there's a #16 situation, note that to prevent #13 + * and #14. + */ + if (!same(remote, head)) { + for (i = 1; i < o->head_idx; i++) { + if (same(stages[i], head)) { + head_match = i; + } + if (same(stages[i], remote)) { + remote_match = i; + } + } + } + + /* We start with cases where the index is allowed to match + * something other than the head: #14(ALT) and #2ALT, where it + * is permitted to match the result instead. + */ + /* #14, #14ALT, #2ALT */ + if (remote && !df_conflict_head && head_match && !remote_match) { + if (index && !same(index, remote) && !same(index, head)) + reject_merge(index); + return merged_entry(remote, index, o); + } + /* + * If we have an entry in the index cache, then we want to + * make sure that it matches head. + */ + if (index && !same(index, head)) { + reject_merge(index); + } + + if (head) { + /* #5ALT, #15 */ + if (same(head, remote)) + return merged_entry(head, index, o); + /* #13, #3ALT */ + if (!df_conflict_remote && remote_match && !head_match) + return merged_entry(head, index, o); + } + + /* #1 */ + if (!head && !remote && any_anc_missing) + return 0; + + /* Under the new "aggressive" rule, we resolve mostly trivial + * cases that we historically had git-merge-one-file resolve. + */ + if (o->aggressive) { + int head_deleted = !head && !df_conflict_head; + int remote_deleted = !remote && !df_conflict_remote; + /* + * Deleted in both. + * Deleted in one and unchanged in the other. + */ + if ((head_deleted && remote_deleted) || + (head_deleted && remote && remote_match) || + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index, o); + else if (path) + verify_absent(path, "removed", o); + return 0; + } + /* + * Added in both, identically. + */ + if (no_anc_exists && head && remote && same(head, remote)) + return merged_entry(head, index, o); + + } + + /* Below are "no merge" cases, which require that the index be + * up-to-date to avoid the files getting overwritten with + * conflict resolution files. + */ + if (index) { + verify_uptodate(index, o); + } + else if (path) + verify_absent(path, "overwritten", o); + + o->nontrivial_merge = 1; + + /* #2, #3, #4, #6, #7, #9, #11. */ + count = 0; + if (!head_match || !remote_match) { + for (i = 1; i < o->head_idx; i++) { + if (stages[i]) { + keep_entry(stages[i]); + count++; + break; + } + } + } +#if DBRT_DEBUG + else { + fprintf(stderr, "read-tree: warning #16 detected\n"); + show_stage_entry(stderr, "head ", stages[head_match]); + show_stage_entry(stderr, "remote ", stages[remote_match]); + } +#endif + if (head) { count += keep_entry(head); } + if (remote) { count += keep_entry(remote); } + return count; +} + +/* + * Two-way merge. + * + * The rule is to "carry forward" what is in the index without losing + * information across a "fast forward", favoring a successful merge + * over a merge failure when it makes sense. For details of the + * "carry forward" rule, please see <Documentation/git-read-tree.txt>. + * + */ +int twoway_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *current = src[0]; + struct cache_entry *oldtree = src[1], *newtree = src[2]; + + if (o->merge_size != 2) + return error("Cannot do a twoway merge of %d trees", + o->merge_size); + + if (current) { + if ((!oldtree && !newtree) || /* 4 and 5 */ + (!oldtree && newtree && + same(current, newtree)) || /* 6 and 7 */ + (oldtree && newtree && + same(oldtree, newtree)) || /* 14 and 15 */ + (oldtree && newtree && + !same(oldtree, newtree) && /* 18 and 19*/ + same(current, newtree))) { + return keep_entry(current); + } + else if (oldtree && !newtree && same(current, oldtree)) { + /* 10 or 11 */ + return deleted_entry(oldtree, current, o); + } + else if (oldtree && newtree && + same(current, oldtree) && !same(current, newtree)) { + /* 20 or 21 */ + return merged_entry(newtree, current, o); + } + else { + /* all other failures */ + if (oldtree) + reject_merge(oldtree); + if (current) + reject_merge(current); + if (newtree) + reject_merge(newtree); + return -1; + } + } + else if (newtree) + return merged_entry(newtree, current, o); + else + return deleted_entry(oldtree, current, o); +} + +/* + * Bind merge. + * + * Keep the index entries at stage0, collapse stage1 but make sure + * stage0 does not have anything there. + */ +int bind_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (o->merge_size != 1) + return error("Cannot do a bind merge of %d trees\n", + o->merge_size); + if (a && old) + die("Entry '%s' overlaps. Cannot bind.", a->name); + if (!a) + return keep_entry(old); + else + return merged_entry(a, NULL, o); +} + +/* + * One-way merge. + * + * The rule is: + * - take the stat information from stage0, take the data from stage1 + */ +int oneway_merge(struct cache_entry **src, + struct unpack_trees_options *o) +{ + struct cache_entry *old = src[0]; + struct cache_entry *a = src[1]; + + if (o->merge_size != 1) + return error("Cannot do a oneway merge of %d trees", + o->merge_size); + + if (!a) + return deleted_entry(old, old, o); + if (old && same(old, a)) { + if (o->reset) { + struct stat st; + if (lstat(old->name, &st) || + ce_match_stat(old, &st, 1)) + old->ce_flags |= htons(CE_UPDATE); + } + return keep_entry(old); + } + return merged_entry(a, old, o); +} diff --git a/unpack-trees.h b/unpack-trees.h new file mode 100644 index 0000000000..c4601621cd --- /dev/null +++ b/unpack-trees.h @@ -0,0 +1,35 @@ +#ifndef UNPACK_TREES_H +#define UNPACK_TREES_H + +struct unpack_trees_options; + +typedef int (*merge_fn_t)(struct cache_entry **src, + struct unpack_trees_options *options); + +struct unpack_trees_options { + int reset; + int merge; + int update; + int index_only; + int nontrivial_merge; + int trivial_merges_only; + int verbose_update; + int aggressive; + const char *prefix; + merge_fn_t fn; + + int head_idx; + int merge_size; + + struct cache_entry *df_conflict_entry; +}; + +extern int unpack_trees(struct object_list *trees, + struct unpack_trees_options *options); + +int threeway_merge(struct cache_entry **stages, struct unpack_trees_options *o); +int twoway_merge(struct cache_entry **src, struct unpack_trees_options *o); +int bind_merge(struct cache_entry **src, struct unpack_trees_options *o); +int oneway_merge(struct cache_entry **src, struct unpack_trees_options *o); + +#endif diff --git a/upload-pack.c b/upload-pack.c index 07ecdb4281..fcf279843a 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -14,14 +14,12 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define THEY_HAVE (1U << 0) #define OUR_REF (1U << 1) #define WANTED (1U << 2) -#define MAX_HAS 256 -#define MAX_NEEDS 256 -static int nr_has = 0, nr_needs = 0, multi_ack = 0, nr_our_refs = 0; -static int use_thin_pack = 0; -static unsigned char has_sha1[MAX_HAS][20]; -static unsigned char needs_sha1[MAX_NEEDS][20]; -static unsigned int timeout = 0; -static int use_sideband = 0; +static int multi_ack, nr_our_refs; +static int use_thin_pack; +static struct object_array have_obj; +static struct object_array want_obj; +static unsigned int timeout; +static int use_sideband; static void reset_timeout(void) { @@ -83,7 +81,7 @@ static void create_pack_file(void) */ int lp_pipe[2], pu_pipe[2], pe_pipe[2]; pid_t pid_rev_list, pid_pack_objects; - int create_full_pack = (nr_our_refs == nr_needs && !nr_has); + int create_full_pack = (nr_our_refs == want_obj.nr && !have_obj.nr); char data[8193], progress[128]; char abort_msg[] = "aborting due to possible repository " "corruption on the remote side."; @@ -107,7 +105,7 @@ static void create_pack_file(void) use_thin_pack = 0; /* no point doing it */ } else - args = nr_has + nr_needs + 5; + args = have_obj.nr + want_obj.nr + 5; p = xmalloc(args * sizeof(char *)); argv = (const char **) p; buf = xmalloc(args * 45); @@ -118,20 +116,22 @@ static void create_pack_file(void) close(lp_pipe[1]); *p++ = "rev-list"; *p++ = use_thin_pack ? "--objects-edge" : "--objects"; - if (create_full_pack || MAX_NEEDS <= nr_needs) + if (create_full_pack) *p++ = "--all"; else { - for (i = 0; i < nr_needs; i++) { + for (i = 0; i < want_obj.nr; i++) { + struct object *o = want_obj.objects[i].item; *p++ = buf; - memcpy(buf, sha1_to_hex(needs_sha1[i]), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } } if (!create_full_pack) - for (i = 0; i < nr_has; i++) { + for (i = 0; i < have_obj.nr; i++) { + struct object *o = have_obj.objects[i].item; *p++ = buf; *buf++ = '^'; - memcpy(buf, sha1_to_hex(has_sha1[i]), 41); + memcpy(buf, sha1_to_hex(o->sha1), 41); buf += 41; } *p++ = NULL; @@ -322,28 +322,29 @@ static void create_pack_file(void) static int got_sha1(char *hex, unsigned char *sha1) { + struct object *o; + if (get_sha1_hex(hex, sha1)) die("git-upload-pack: expected SHA1 object, got '%s'", hex); if (!has_sha1_file(sha1)) return 0; - if (nr_has < MAX_HAS) { - struct object *o = lookup_object(sha1); - if (!(o && o->parsed)) - o = parse_object(sha1); - if (!o) - die("oops (%s)", sha1_to_hex(sha1)); - if (o->type == OBJ_COMMIT) { - struct commit_list *parents; - if (o->flags & THEY_HAVE) - return 0; - o->flags |= THEY_HAVE; - for (parents = ((struct commit*)o)->parents; - parents; - parents = parents->next) - parents->item->object.flags |= THEY_HAVE; - } - memcpy(has_sha1[nr_has++], sha1, 20); + + o = lookup_object(sha1); + if (!(o && o->parsed)) + o = parse_object(sha1); + if (!o) + die("oops (%s)", sha1_to_hex(sha1)); + if (o->type == OBJ_COMMIT) { + struct commit_list *parents; + if (o->flags & THEY_HAVE) + return 0; + o->flags |= THEY_HAVE; + for (parents = ((struct commit*)o)->parents; + parents; + parents = parents->next) + parents->item->object.flags |= THEY_HAVE; } + add_object_array(o, NULL, &have_obj); return 1; } @@ -361,26 +362,24 @@ static int get_common_commits(void) reset_timeout(); if (!len) { - if (nr_has == 0 || multi_ack) + if (have_obj.nr == 0 || multi_ack) packet_write(1, "NAK\n"); continue; } len = strip(line, len); if (!strncmp(line, "have ", 5)) { if (got_sha1(line+5, sha1) && - (multi_ack || nr_has == 1)) { - if (nr_has >= MAX_HAS) - multi_ack = 0; + (multi_ack || have_obj.nr == 1)) { packet_write(1, "ACK %s%s\n", - sha1_to_hex(sha1), - multi_ack ? " continue" : ""); + sha1_to_hex(sha1), + multi_ack ? " continue" : ""); if (multi_ack) memcpy(last_sha1, sha1, 20); } continue; } if (!strcmp(line, "done")) { - if (nr_has > 0) { + if (have_obj.nr > 0) { if (multi_ack) packet_write(1, "ACK %s\n", sha1_to_hex(last_sha1)); @@ -393,31 +392,21 @@ static int get_common_commits(void) } } -static int receive_needs(void) +static void receive_needs(void) { static char line[1000]; - int len, needs; + int len; - needs = 0; for (;;) { struct object *o; - unsigned char dummy[20], *sha1_buf; + unsigned char sha1_buf[20]; len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) - return needs; - - sha1_buf = dummy; - if (needs == MAX_NEEDS) { - fprintf(stderr, - "warning: supporting only a max of %d requests. " - "sending everything instead.\n", - MAX_NEEDS); - } - else if (needs < MAX_NEEDS) - sha1_buf = needs_sha1[needs]; + return; - if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf)) + if (strncmp("want ", line, 5) || + get_sha1_hex(line+5, sha1_buf)) die("git-upload-pack: protocol error, " "expected to get sha, not '%s'", line); if (strstr(line+45, "multi_ack")) @@ -440,7 +429,7 @@ static int receive_needs(void) die("git-upload-pack: not our ref %s", line+5); if (!(o->flags & WANTED)) { o->flags |= WANTED; - needs++; + add_object_array(o, NULL, &want_obj); } } } @@ -470,18 +459,17 @@ static int send_ref(const char *refname, const unsigned char *sha1) return 0; } -static int upload_pack(void) +static void upload_pack(void) { reset_timeout(); head_ref(send_ref); for_each_ref(send_ref); packet_flush(1); - nr_needs = receive_needs(); - if (!nr_needs) - return 0; - get_common_commits(); - create_pack_file(); - return 0; + receive_needs(); + if (want_obj.nr) { + get_common_commits(); + create_pack_file(); + } } int main(int argc, char **argv) |