caret=^
startsb=[
endsb=]
+tilde=~
ifdef::backend-docbook[]
[gitlink-inlinemacro]
Can be overridden by the 'GIT_HTTP_LOW_SPEED_LIMIT' and
'GIT_HTTP_LOW_SPEED_TIME' environment variables.
+http.noEPSV::
+ A boolean which disables using of EPSV ftp command by curl.
+ This can helpful with some "poor" ftp servers which doesn't
+ support EPSV mode. Can be overridden by the 'GIT_CURL_FTP_NO_EPSV'
+ environment variable. Default is false (curl will use EPSV).
+
i18n.commitEncoding::
Character encoding the commit messages are stored in; git itself
does not care per se, but this information is necessary e.g. when
--patch-with-raw::
Synonym for "-p --raw".
---stat::
- Generate a diffstat.
+--stat[=width[,name-width]]::
+ Generate a diffstat. You can override the default
+ output width for 80-column terminal by "--stat=width".
+ The width of the filename part can be controlled by
+ giving another width to it separated by a comma.
+
+--numstat::
+ Similar to \--stat, but shows number of added and
+ deleted lines in decimal notation and pathname without
+ abbreviation, to make it more machine friendly.
--summary::
Output a condensed summary of extended header information
SYNOPSIS
--------
-'git-cherry-pick' [--edit] [-n] [-r] <commit>
+'git-cherry-pick' [--edit] [-n] [-x] <commit>
DESCRIPTION
-----------
With this option, `git-cherry-pick` will let you edit the commit
message prior committing.
--r|--replay::
- Usually the command appends which commit was
+-x::
+ Cause the command to append which commit was
cherry-picked after the original commit message when
- making a commit. This option, '--replay', causes it to
- use the original commit message intact. This is useful
- when you are reordering the patches in your private tree
- before publishing.
+ making a commit. Do not use this option if you are
+ cherry-picking from your private branch because the
+ information is useless to the recipient. If on the
+ other hand you are cherry-picking between two publicly
+ visible branches (e.g. backporting a fix to a
+ maintenance branch for an older release from a
+ development branch), adding this information can be
+ useful.
+
+-r|--replay::
+ It used to be that the command defaulted to do `-x`
+ described above, and `-r` was to disable it. Now the
+ default is not to do `-x` so this option is a no-op.
-n|--no-commit::
Usually the command automatically creates a commit with
Report the list of objects being walked locally and the
list of objects successfully sent to the remote repository.
-<ref>...:
+<ref>...::
The remote refs to update.
--all::
This implies `--revs`. In addition to the list of
revision arguments read from the standard input, pretend
- as if all refs under `$GIT_DIR/refs` are specifed to be
+ as if all refs under `$GIT_DIR/refs` are specified to be
included.
---window and --depth::
- These two options affects how the objects contained in
+--window=[N], --depth=[N]::
+ These two options affect how the objects contained in
the pack are stored using delta compression. The
objects are first internally sorted by type, size and
optionally names and compared against the other objects
it too deep affects the performance on the unpacker
side, because delta data needs to be applied that many
times to get to the necessary object.
+ The default value for both --window and --depth is 10.
--incremental::
This flag causes an object already in a pack ignored
SYNOPSIS
--------
-'git-rebase' [--merge] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-v] [--merge] [--onto <newbase>] <upstream> [<branch>]
'git-rebase' --continue | --skip | --abort
is used instead (`git-merge-recursive` when merging a single
head, `git-merge-octopus` otherwise). This implies --merge.
+-v, \--verbose::
+ Display a diffstat of what changed upstream since the last rebase.
+
include::merge-strategies.txt[]
NOTES
`git update-server-info`.
--window=[N], --depth=[N]::
- These two options affects how the objects contained in the pack are
+ These two options affect how the objects contained in the pack are
stored using delta compression. The objects are first internally
sorted by type, size and optionally names and compared against the
other objects within `--window` to see if using delta compression saves
space. `--depth` limits the maximum delta depth; making it too deep
affects the performance on the unpacker side, because delta data needs
to be applied that many times to get to the necessary object.
+ The default value for both --window and --depth is 10.
Author
--get::
Get the value for a given key (optionally filtered by a regex
- matching the value).
+ matching the value). Returns error code 1 if the key was not
+ found and error code 2 if multiple key values were found.
--get-all::
Like get, but does not fail if the number of values for the key
A revision parameter typically, but not necessarily, names a
commit object. They use what is called an 'extended SHA1'
-syntax.
+syntax. Here are various ways to spell object names. The
+ones listed near the end of this list are to name trees and
+blobs contained in a commit.
* The full SHA1 object name (40-byte hexadecimal string), or
a substring of such that is unique within the repository.
name the same commit object if there are no other object in
your repository whose object name starts with dae86e.
+* An output from `git-describe`; i.e. a closest tag, followed by a
+ dash, a 'g', and an abbreviated object name.
+
* A symbolic ref name. E.g. 'master' typically means the commit
object referenced by $GIT_DIR/refs/heads/master. If you
happen to have both heads/master and tags/master, you can
'rev{caret}0' means the commit itself and is used when 'rev' is the
object name of a tag object that refers to a commit object.
-* A suffix '~<n>' to a revision parameter means the commit
+* A suffix '{tilde}<n>' to a revision parameter means the commit
object that is the <n>th generation grand-parent of the named
commit object, following only the first parent. I.e. rev~3 is
equivalent to rev{caret}{caret}{caret} which is equivalent to\
and dereference the tag recursively until a non-tag object is
found.
+* A suffix ':' followed by a path; this names the blob or tree
+ at the given path in the tree-ish object named by the part
+ before the colon.
+
+* A colon, optionally followed by a stage number (0 to 3) and a
+ colon, followed by a path; this names a blob object in the
+ index at the given path. Missing stage number (and the colon
+ that follows it) names an stage 0 entry.
+
Here is an illustration, by Jon Loeliger. Both node B and C are
a commit parents of commit node A. Parent commits are ordered
left-to-right.
<directory>::
The repository to update.
-<ref>...:
+<ref>...::
The remote refs to update.
SYNOPSIS
--------
-git-log --pretty=short | 'git-shortlog'
+git-log --pretty=short | 'git-shortlog' [-h] [-n] [-s]
DESCRIPTION
-----------
Summarizes 'git log' output in a format suitable for inclusion
-in release announcements. Each commit will be grouped by author
+in release announcements. Each commit will be grouped by author and
the first line of the commit message will be shown.
Additionally, "[PATCH]" will be stripped from the commit description.
+OPTIONS
+-------
+
+-h::
+ Print a short usage message and exit.
+
+-n::
+ Sort output according to the number of commits per author instead
+ of author alphabetic order.
+
+-s::
+ Supress commit description and provide a commit count summary only.
+
FILES
-----
'.mailmap'::
'init'::
Creates an empty git repository with additional metadata
directories for git-svn. The Subversion URL must be specified
- as a command-line argument.
+ as a command-line argument. Optionally, the target directory
+ to operate on can be specified as a second argument. Normally
+ this command initializes the current directory.
'fetch'::
This is advantageous over 'commit' (below) because it produces
cleaner, more linear history.
+'log'::
+ This should make it easy to look up svn log messages when svn
+ users refer to -r/--revision numbers.
+
+ The following features from `svn log' are supported:
+
+ --revision=<n>[:<n>] - is supported, non-numeric args are not:
+ HEAD, NEXT, BASE, PREV, etc ...
+ -v/--verbose - it's not completely compatible with
+ the --verbose output in svn log, but
+ reasonably close.
+ --limit=<n> - is NOT the same as --max-count,
+ doesn't count merged/excluded commits
+ --incremental - supported
+
+ New features:
+
+ --show-commit - shows the git commit sha1, as well
+ --oneline - our version of --pretty=oneline
+
+ Any other arguments are passed directly to `git log'
+
'commit'::
+ You should consider using 'dcommit' instead of this command.
Commit specified commit or tree objects to SVN. This relies on
your imported fetch data being up-to-date. This makes
absolutely no attempts to do patching when committing to SVN, it
directories. The output is suitable for appending to
the $GIT_DIR/info/exclude file.
+'commit-diff'::
+ Commits the diff of two tree-ish arguments from the
+ command-line. This command is intended for interopability with
+ git-svnimport and does not rely on being inside an git-svn
+ init-ed repository. This command takes three arguments, (a) the
+ original tree to diff against, (b) the new tree result, (c) the
+ URL of the target Subversion repository. The final argument
+ (URL) may be omitted if you are working from a git-svn-aware
+ repository (that has been init-ed with git-svn).
+
+'graft-branches'::
+ This command attempts to detect merges/branches from already
+ imported history. Techniques used currently include regexes,
+ file copies, and tree-matches). This command generates (or
+ modifies) the $GIT_DIR/info/grafts file. This command is
+ considered experimental, and inherently flawed because
+ merge-tracking in SVN is inherently flawed and inconsistent
+ across different repositories.
+
+'multi-init'::
+ This command supports git-svnimport-like command-line syntax for
+ importing repositories that are layed out as recommended by the
+ SVN folks. This is a bit more tolerant than the git-svnimport
+ command-line syntax and doesn't require the user to figure out
+ where the repository URL ends and where the repository path
+ begins.
+
+'multi-fetch'::
+ This runs fetch on all known SVN branches we're tracking. This
+ will NOT discover new branches (unlike git-svnimport), so
+ multi-init will need to be re-run (it's idempotent).
+
--
OPTIONS
-------
--
+--shared::
+--template=<template_directory>::
+ Only used with the 'init' command.
+ These are passed directly to gitlink:git-init-db[1].
+
-r <ARG>::
--revision <ARG>::
--rmdir::
-Only used with the 'commit' command.
+Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
Remove directories from the SVN tree if there are no files left
behind. SVN can version empty directories, and they are not
-e::
--edit::
-Only used with the 'commit' command.
+Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
Edit the commit message before committing to SVN. This is off by
default for objects that are commits, and forced on when committing
-l<num>::
--find-copies-harder::
-Both of these are only used with the 'commit' command.
+Only used with the 'dcommit', 'commit' and 'commit-diff' commands.
They are both passed directly to git-diff-tree see
gitlink:git-diff-tree[1] for more information.
appropriate entry. Re-running the previous git-svn command
after the authors-file is modified should continue operation.
-repo-config key: svn.authors-file
+repo-config key: svn.authorsfile
+
+-q::
+--quiet::
+ Make git-svn less verbose. This only affects git-svn if you
+ have the SVN::* libraries installed and are using them.
+
+--repack[=<n>]::
+--repack-flags=<flags>
+ These should help keep disk usage sane for large fetches
+ with many revisions.
+
+ --repack takes an optional argument for the number of revisions
+ to fetch before repacking. This defaults to repacking every
+ 1000 commits fetched if no argument is specified.
+
+ --repack-flags are passed directly to gitlink:git-repack[1].
+
+repo-config key: svn.repack
+repo-config key: svn.repackflags
-m::
--merge::
'<<tracking-multiple-repos,Tracking Multiple Repositories or Branches>>'
for more information on using GIT_SVN_ID.
+--follow-parent::
+ This is especially helpful when we're tracking a directory
+ that has been moved around within the repository, or if we
+ started tracking a branch and never tracked the trunk it was
+ descended from.
+
+ This relies on the SVN::* libraries to work.
+
+repo-config key: svn.followparent
+
+--no-metadata::
+ This gets rid of the git-svn-id: lines at the end of every commit.
+
+ With this, you lose the ability to use the rebuild command. If
+ you ever lose your .git/svn/git-svn/.rev_db file, you won't be
+ able to fetch again, either. This is fine for one-shot imports.
+
+ The 'git-svn log' command will not work on repositories using this,
+ either.
+
+repo-config key: svn.nometadata
+
--
COMPATIBILITY OPTIONS
--no-ignore-externals::
Only used with the 'fetch' and 'rebuild' command.
+This command has no effect when you are using the SVN::*
+libraries with git, svn:externals are always avoided.
+
By default, git-svn passes --ignore-externals to svn to avoid
fetching svn:external trees into git. Pass this flag to enable
externals tracking directly via git.
Tracking and contributing to an Subversion managed-project:
------------------------------------------------------------------------
-# Initialize a tree (like git init-db):
+# Initialize a repo (like git init-db):
git-svn init http://svn.foo.org/project/trunk
# Fetch remote revisions:
git-svn fetch
hack to allow it to track an arbitrary number of related _or_ unrelated
SVN repositories via one git repository. Simply set the GIT_SVN_ID
environment variable to a name other other than "git-svn" (the default)
-and git-svn will ignore the contents of the $GIT_DIR/git-svn directory
-and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that
+and git-svn will ignore the contents of the $GIT_DIR/svn/git-svn directory
+and instead do all of its work in $GIT_DIR/svn/$GIT_SVN_ID for that
invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of
remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified
by the user outside of git-svn commands.
Advanced Example: Tracking a Reorganized Repository
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Note: this example is now obsolete if you have SVN::* libraries
+installed. Simply use --follow-parent when fetching.
+
If you're tracking a directory that has moved, or otherwise been
branched or tagged off of another directory in the repository and you
care about the full history of the project, then you can read this
BUGS
----
-If somebody commits a conflicting changeset to SVN at a bad moment
-(right before you commit) causing a conflict and your commit to fail,
-your svn working tree ($GIT_DIR/git-svn/tree) may be dirtied. The
-easiest thing to do is probably just to rm -rf $GIT_DIR/git-svn/tree and
-run 'rebuild'.
+
+If you are not using the SVN::* Perl libraries and somebody commits a
+conflicting changeset to SVN at a bad moment (right before you commit)
+causing a conflict and your commit to fail, your svn working tree
+($GIT_DIR/git-svn/tree) may be dirtied. The easiest thing to do is
+probably just to rm -rf $GIT_DIR/git-svn/tree and run 'rebuild'.
We ignore all SVN properties except svn:executable. Too difficult to
map them since we rely heavily on git write-tree being _exactly_ the
same on both the SVN and git working trees and I prefer not to clutter
working trees with metadata files.
-svn:keywords can't be ignored in Subversion (at least I don't know of
-a way to ignore them).
-
Renamed and copied directories are not detected by git and hence not
tracked when committing to SVN. I do not plan on adding support for
this as it's quite difficult and time-consuming to get working for all
Updates auxiliary information on a dumb server to help
clients discover references and packs on it.
+gitlink:git-upload-archive[1]::
+ Invoked by 'git-archive' to send a generated archive.
+
gitlink:git-upload-pack[1]::
Invoked by 'git-fetch-pack' to push
what are asked for.
character hexadecimal encoding of the hash of the object (possibly
followed by a white space).
-object type:
+object type::
One of the identifiers "commit","tree","tag" and "blob" describing
the type of an object.
A tag is most typically used to mark a particular point in the
commit ancestry chain.
-unmerged index:
+unmerged index::
An index which contains unmerged index entries.
working tree::
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v1.4.2.GIT
+DEF_VER=v1.4.3.GIT
LF='
'
has been actively developed since 1997, and people have moved over to
graphical file managers.
+ - You can use git after building but without installing if you
+ wanted to. Various git commands need to find other git
+ commands and scripts to do their work, so you would need to
+ arrange a few environment variables to tell them that their
+ friends will be found in your built source area instead of at
+ their standard installation area. Something like this works
+ for me:
+
+ GIT_EXEC_PATH=`pwd`
+ PATH=`pwd`:$PATH
+ GITPERLLIB=`pwd`/perl/blib/lib
+ export GIT_EXEC_PATH PATH GITPERLLIB
+
- Git is reasonably self-sufficient, but does depend on a few external
programs and libraries:
# The default target of this Makefile is...
all:
-# Define MOZILLA_SHA1 environment variable when running make to make use of
-# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
-# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
-# choice) has very fast version optimized for i586.
-#
# Define NO_OPENSSL environment variable if you do not have OpenSSL.
# This also implies MOZILLA_SHA1.
#
# Define ARM_SHA1 environment variable when running make to make use of
# a bundled SHA1 routine optimized for ARM.
#
+# Define MOZILLA_SHA1 environment variable when running make to make use of
+# a bundled SHA1 routine coming from Mozilla. It is GPL'd and should be fast
+# on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default
+# choice) has very fast version optimized for i586.
+#
# Define NEEDS_SSL_WITH_CRYPTO if you need -lcrypto with -lssl (Darwin).
#
# Define NEEDS_LIBICONV if linking with libc is not enough (Darwin).
# 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.
-
+#
# Define USE_NSEC below if you want git to care about sub-second file mtimes
# and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and
# it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely
# randomly break unless your underlying filesystem supports those sub-second
# times (my ext3 doesn't).
-
+#
# Define USE_STDEV below if you want git to care about the underlying device
# change being considered an inode change from the update-cache perspective.
### --- END CONFIGURATION SECTION ---
+# Those must not be GNU-specific; they are shared with perl/ which may
+# be built by a different compiler. (Note that this is an artifact now
+# but it still might be nice to keep that distinction.)
+BASIC_CFLAGS =
+BASIC_LDFLAGS =
+
SCRIPT_SH = \
git-bisect.sh git-branch.sh git-checkout.sh \
git-cherry.sh git-clean.sh git-clone.sh git-commit.sh \
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
-ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
+ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) \
+ git-merge-recur$X
# Backward compatibility -- to be removed after 1.0
PROGRAMS += git-ssh-pull$X git-ssh-push$X
builtin-write-tree.o
GITLIBS = $(LIB_FILE) $(XDIFF_LIB)
-LIBS = $(GITLIBS) -lz
+EXTLIBS = -lz
#
# Platform specific tweaks
NO_STRLCPY = YesPlease
ifndef NO_FINK
ifeq ($(shell test -d /sw/lib && echo y),y)
- ALL_CFLAGS += -I/sw/include
- ALL_LDFLAGS += -L/sw/lib
+ BASIC_CFLAGS += -I/sw/include
+ BASIC_LDFLAGS += -L/sw/lib
endif
endif
ifndef NO_DARWIN_PORTS
ifeq ($(shell test -d /opt/local/lib && echo y),y)
- ALL_CFLAGS += -I/opt/local/include
- ALL_LDFLAGS += -L/opt/local/lib
+ BASIC_CFLAGS += -I/opt/local/include
+ BASIC_LDFLAGS += -L/opt/local/lib
endif
endif
endif
endif
INSTALL = ginstall
TAR = gtar
- ALL_CFLAGS += -D__EXTENSIONS__
+ BASIC_CFLAGS += -D__EXTENSIONS__
endif
ifeq ($(uname_O),Cygwin)
NO_D_TYPE_IN_DIRENT = YesPlease
endif
ifeq ($(uname_S),FreeBSD)
NEEDS_LIBICONV = YesPlease
- ALL_CFLAGS += -I/usr/local/include
- ALL_LDFLAGS += -L/usr/local/lib
+ BASIC_CFLAGS += -I/usr/local/include
+ BASIC_LDFLAGS += -L/usr/local/lib
endif
ifeq ($(uname_S),OpenBSD)
NO_STRCASESTR = YesPlease
NEEDS_LIBICONV = YesPlease
- ALL_CFLAGS += -I/usr/local/include
- ALL_LDFLAGS += -L/usr/local/lib
+ BASIC_CFLAGS += -I/usr/local/include
+ BASIC_LDFLAGS += -L/usr/local/lib
endif
ifeq ($(uname_S),NetBSD)
ifeq ($(shell expr "$(uname_R)" : '[01]\.'),2)
NEEDS_LIBICONV = YesPlease
endif
- ALL_CFLAGS += -I/usr/pkg/include
- ALL_LDFLAGS += -L/usr/pkg/lib -Wl,-rpath,/usr/pkg/lib
+ BASIC_CFLAGS += -I/usr/pkg/include
+ BASIC_LDFLAGS += -L/usr/pkg/lib
+ ALL_LDFLAGS += -Wl,-rpath,/usr/pkg/lib
endif
ifeq ($(uname_S),AIX)
NO_STRCASESTR=YesPlease
NO_STRLCPY = YesPlease
NO_SOCKADDR_STORAGE=YesPlease
SHELL_PATH=/usr/gnu/bin/bash
- ALL_CFLAGS += -DPATH_MAX=1024
+ BASIC_CFLAGS += -DPATH_MAX=1024
# for now, build 32-bit version
- ALL_LDFLAGS += -L/usr/lib32
+ BASIC_LDFLAGS += -L/usr/lib32
endif
ifneq (,$(findstring arm,$(uname_M)))
ARM_SHA1 = YesPlease
ifndef NO_CURL
ifdef CURLDIR
# This is still problematic -- gcc does not always want -R.
- ALL_CFLAGS += -I$(CURLDIR)/include
+ BASIC_CFLAGS += -I$(CURLDIR)/include
CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl
else
CURL_LIBCURL = -lcurl
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
# Again this may be problematic -- gcc does not always want -R.
- ALL_CFLAGS += -I$(OPENSSLDIR)/include
+ BASIC_CFLAGS += -I$(OPENSSLDIR)/include
OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib
else
OPENSSL_LINK =
endif
else
- ALL_CFLAGS += -DNO_OPENSSL
+ BASIC_CFLAGS += -DNO_OPENSSL
MOZILLA_SHA1 = 1
OPENSSL_LIBSSL =
endif
ifdef NEEDS_LIBICONV
ifdef ICONVDIR
# Again this may be problematic -- gcc does not always want -R.
- ALL_CFLAGS += -I$(ICONVDIR)/include
+ BASIC_CFLAGS += -I$(ICONVDIR)/include
ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib
else
ICONV_LINK =
endif
- LIBS += $(ICONV_LINK) -liconv
+ EXTLIBS += $(ICONV_LINK) -liconv
endif
ifdef NEEDS_SOCKET
- LIBS += -lsocket
+ EXTLIBS += -lsocket
SIMPLE_LIB += -lsocket
endif
ifdef NEEDS_NSL
- LIBS += -lnsl
+ EXTLIBS += -lnsl
SIMPLE_LIB += -lnsl
endif
ifdef NO_D_TYPE_IN_DIRENT
- ALL_CFLAGS += -DNO_D_TYPE_IN_DIRENT
+ BASIC_CFLAGS += -DNO_D_TYPE_IN_DIRENT
endif
ifdef NO_D_INO_IN_DIRENT
- ALL_CFLAGS += -DNO_D_INO_IN_DIRENT
+ BASIC_CFLAGS += -DNO_D_INO_IN_DIRENT
endif
ifdef NO_C99_FORMAT
ALL_CFLAGS += -DNO_C99_FORMAT
endif
ifdef NO_SYMLINK_HEAD
- ALL_CFLAGS += -DNO_SYMLINK_HEAD
+ BASIC_CFLAGS += -DNO_SYMLINK_HEAD
endif
ifdef NO_STRCASESTR
COMPAT_CFLAGS += -DNO_STRCASESTR
COMPAT_OBJS += compat/mmap.o
endif
ifdef NO_IPV6
- ALL_CFLAGS += -DNO_IPV6
+ BASIC_CFLAGS += -DNO_IPV6
endif
ifdef NO_SOCKADDR_STORAGE
ifdef NO_IPV6
- ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in
+ BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in
else
- ALL_CFLAGS += -Dsockaddr_storage=sockaddr_in6
+ BASIC_CFLAGS += -Dsockaddr_storage=sockaddr_in6
endif
endif
ifdef NO_INET_NTOP
endif
ifdef NO_ICONV
- ALL_CFLAGS += -DNO_ICONV
+ BASIC_CFLAGS += -DNO_ICONV
endif
ifdef PPC_SHA1
LIB_OBJS += mozilla-sha1/sha1.o
else
SHA1_HEADER = <openssl/sha.h>
- LIBS += $(LIB_4_CRYPTO)
+ EXTLIBS += $(LIB_4_CRYPTO)
endif
endif
endif
ifdef NO_ACCURATE_DIFF
- ALL_CFLAGS += -DNO_ACCURATE_DIFF
+ BASIC_CFLAGS += -DNO_ACCURATE_DIFF
endif
# Shell quote (do not use $(call) to accommodate ancient setups);
PYTHON_PATH_SQ = $(subst ','\'',$(PYTHON_PATH))
GIT_PYTHON_DIR_SQ = $(subst ','\'',$(GIT_PYTHON_DIR))
-ALL_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
+LIBS = $(GITLIBS) $(EXTLIBS)
+
+BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' $(COMPAT_CFLAGS)
LIB_OBJS += $(COMPAT_OBJS)
+
+ALL_CFLAGS += $(BASIC_CFLAGS)
+ALL_LDFLAGS += $(BASIC_LDFLAGS)
+
export prefix TAR INSTALL DESTDIR SHELL_PATH template_dir
+
+
### Build rules
-all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi \
- git-merge-recur$X
+all: $(ALL_PROGRAMS) $(BUILT_INS) git$X gitk gitweb/gitweb.cgi
-all:
+all: perl/Makefile
+ $(MAKE) -C perl
$(MAKE) -C templates
strip: $(PROGRAMS) git$X
chmod +x $@+
mv $@+ $@
-$(patsubst %.perl,%,$(SCRIPT_PERL)) : % : %.perl
+$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/Makefile
+$(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
rm -f $@ $@+
- sed -e '1s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+ INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
+ sed -e '1{' \
+ -e ' s|#!.*perl|#!$(PERL_PATH_SQ)|' \
+ -e ' h' \
+ -e ' s=.*=use lib (split(/:/, $$ENV{GITPERLLIB} || "@@INSTLIBDIR@@"));=' \
+ -e ' H' \
+ -e ' x' \
+ -e '}' \
+ -e 's|@@INSTLIBDIR@@|'"$$INSTLIBDIR"'|g' \
-e 's/@@GIT_VERSION@@/$(GIT_VERSION)/g' \
$@.perl >$@+
chmod +x $@+
rm -f $@ && $(AR) rcs $@ $(LIB_OBJS)
XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o
+$(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \
+ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h
$(XDIFF_LIB): $(XDIFF_OBJS)
rm -f $@ && $(AR) rcs $@ $(XDIFF_OBJS)
+perl/Makefile: perl/Git.pm perl/Makefile.PL GIT-CFLAGS
+ (cd perl && $(PERL_PATH) Makefile.PL \
+ PREFIX='$(prefix_SQ)')
+
doc:
$(MAKE) -C Documentation all
$(INSTALL) $(ALL_PROGRAMS) '$(DESTDIR_SQ)$(gitexecdir_SQ)'
$(INSTALL) git$X gitk '$(DESTDIR_SQ)$(bindir_SQ)'
$(MAKE) -C templates DESTDIR='$(DESTDIR_SQ)' install
+ $(MAKE) -C perl install
$(INSTALL) -d -m755 '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
$(INSTALL) $(PYMODULES) '$(DESTDIR_SQ)$(GIT_PYTHON_DIR_SQ)'
if test 'z$(bindir_SQ)' != 'z$(gitexecdir_SQ)'; \
mv $@+ $@
GIT_TARNAME=git-$(GIT_VERSION)
-dist: git.spec git-tar-tree
- ./git-tar-tree HEAD^{tree} $(GIT_TARNAME) > $(GIT_TARNAME).tar
+dist: git.spec git-archive
+ ./git-archive --format=tar \
+ --prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
@mkdir -p $(GIT_TARNAME)
@cp git.spec $(GIT_TARNAME)
@echo $(GIT_VERSION) > $(GIT_TARNAME)/version
rm -f $(htmldocs).tar.gz $(manpages).tar.gz
rm -f gitweb/gitweb.cgi
$(MAKE) -C Documentation/ clean
- $(MAKE) -C templates clean
+ [ ! -f perl/Makefile ] || $(MAKE) -C perl/ clean || $(MAKE) -C perl/ clean
+ rm -f perl/ppport.h perl/Makefile.old
+ $(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
rm -f GIT-VERSION-FILE GIT-CFLAGS
{
struct zip_local_header header;
struct zip_dir_header dirent;
+ unsigned long attr2;
unsigned long compressed_size;
unsigned long uncompressed_size;
unsigned long crc;
if (S_ISDIR(mode)) {
method = 0;
+ attr2 = 16;
result = READ_TREE_RECURSIVE;
out = NULL;
uncompressed_size = 0;
compressed_size = 0;
- } else if (S_ISREG(mode)) {
- method = zlib_compression_level == 0 ? 0 : 8;
+ } else if (S_ISREG(mode) || S_ISLNK(mode)) {
+ method = 0;
+ attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
+ if (S_ISREG(mode) && zlib_compression_level != 0)
+ method = 8;
result = 0;
buffer = read_sha1_file(sha1, type, &size);
if (!buffer)
}
copy_le32(dirent.magic, 0x02014b50);
- copy_le16(dirent.creator_version, 0);
- copy_le16(dirent.version, 20);
+ copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
+ copy_le16(dirent.version, 10);
copy_le16(dirent.flags, 0);
copy_le16(dirent.compression_method, method);
copy_le16(dirent.mtime, zip_time);
copy_le16(dirent.comment_length, 0);
copy_le16(dirent.disk, 0);
copy_le16(dirent.attr1, 0);
- copy_le32(dirent.attr2, 0);
+ copy_le32(dirent.attr2, attr2);
copy_le32(dirent.offset, zip_offset);
memcpy(zip_dir + zip_dir_offset, &dirent, sizeof(struct zip_dir_header));
zip_dir_offset += sizeof(struct zip_dir_header);
zip_dir_entries++;
copy_le32(header.magic, 0x04034b50);
- copy_le16(header.version, 20);
+ copy_le16(header.version, 10);
copy_le16(header.flags, 0);
copy_le16(header.compression_method, method);
copy_le16(header.mtime, zip_time);
static char *gitdiff_verify_name(const char *line, int isnull, char *orig_name, const char *oldnew)
{
if (!orig_name && !isnull)
- return find_name(line, NULL, 1, 0);
+ return find_name(line, NULL, 1, TERM_TAB);
if (orig_name) {
int len;
len = strlen(name);
if (isnull)
die("git-apply: bad git-diff - expected /dev/null, got %s on line %d", name, linenr);
- another = find_name(line, NULL, 1, 0);
+ another = find_name(line, NULL, 1, TERM_TAB);
if (!another || memcmp(another, name, len))
die("git-apply: bad git-diff - inconsistent %s filename on line %d", oldnew, linenr);
free(another);
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned char sha1[20];
- unsigned char hdr[50];
- int hdrlen;
/* For safety, we require patch index line to contain
* full 40-byte textual SHA1 for old and new, at least for now.
/* See if the old one matches what the patch
* applies to.
*/
- write_sha1_file_prepare(desc->buffer, desc->size,
- blob_type, sha1, hdr, &hdrlen);
+ hash_sha1_file(desc->buffer, desc->size, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
return error("the patch applies to '%s' (%s), "
"which does not match the "
name);
/* verify that the result matches */
- write_sha1_file_prepare(desc->buffer, desc->size, blob_type,
- sha1, hdr, &hdrlen);
+ hash_sha1_file(desc->buffer, desc->size, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)", name, patch->new_sha1_prefix, sha1_to_hex(sha1));
}
quote_c_style(name, NULL, stdout, 0);
else
fputs(name, stdout);
- putchar('\n');
+ putchar(line_termination);
}
}
die("git-archive: expected a flush");
/* Now, start reading from fd[0] and spit it out to stdout */
- rv = recv_sideband("archive", fd[0], 1, 2, buf, sizeof(buf));
+ rv = recv_sideband("archive", fd[0], 1, 2);
close(fd[0]);
rv |= finish_connect(pid);
if (!rev.diffopt.output_format)
rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT | DIFF_FORMAT_PATCH;
+ if (!output_directory)
+ output_directory = prefix;
+
if (output_directory) {
if (use_stdout)
die("standard output, or directory, which one?");
for (i = 0; i < deleted.nr; i++) {
const char *path = deleted.items[i].path;
remove_file_from_cache(path);
+ cache_tree_invalidate_path(active_cache_tree, path);
}
if (active_cache_changed) {
int is_refspec;
char *s, *p;
- if (!strncmp("URL: ", buffer, 5)) {
+ if (!strncmp("URL:", buffer, 4)) {
is_refspec = 0;
- s = buffer + 5;
- } else if (!strncmp("Push: ", buffer, 6)) {
+ s = buffer + 4;
+ } else if (!strncmp("Push:", buffer, 5)) {
is_refspec = 1;
- s = buffer + 6;
+ s = buffer + 5;
} else
continue;
if (do_all)
ret = !seen;
else
- ret = (seen == 1) ? 0 : 1;
+ ret = (seen == 1) ? 0 : seen > 1 ? 2 : 1;
free_strings:
free(repo_config);
#endif
}
- if (dryrun) {
- unsigned char hdr[200];
- int hdrlen;
- write_sha1_file_prepare(buffer, offset, tree_type, it->sha1,
- hdr, &hdrlen);
- }
+ if (dryrun)
+ hash_sha1_file(buffer, offset, tree_type, it->sha1);
else
write_sha1_file(buffer, offset, tree_type, it->sha1);
free(buffer);
extern int sha1_object_info(const unsigned char *, char *, unsigned long *);
extern void * unpack_sha1_file(void *map, unsigned long mapsize, char *type, unsigned long *size);
extern void * read_sha1_file(const unsigned char *sha1, char *type, unsigned long *size);
+extern int hash_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *sha1);
extern int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *return_sha1);
-extern char *write_sha1_file_prepare(void *buf,
- unsigned long len,
- const char *type,
- unsigned char *sha1,
- unsigned char *hdr,
- int *hdrlen);
extern int check_sha1_signature(const unsigned char *sha1, void *buf, unsigned long size, const char *type);
/* show stat against the first parent even
* when doing combined diff.
*/
- if (i == 0 && opt->output_format & DIFF_FORMAT_DIFFSTAT)
- diffopts.output_format = DIFF_FORMAT_DIFFSTAT;
+ int stat_opt = (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT));
+ if (i == 0 && stat_opt)
+ diffopts.output_format = stat_opt;
else
diffopts.output_format = DIFF_FORMAT_NO_OUTPUT;
diff_tree_sha1(parent[i], sha1, "", &diffopts);
}
needsep = 1;
}
- else if (opt->output_format & DIFF_FORMAT_DIFFSTAT)
+ else if (opt->output_format &
+ (DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIFFSTAT))
needsep = 1;
if (opt->output_format & DIFF_FORMAT_PATCH) {
if (needsep)
while (parent) {
struct commit *p = parent->item;
- const char *hex = abbrev
- ? find_unique_abbrev(p->object.sha1, abbrev)
- : sha1_to_hex(p->object.sha1);
- const char *dots = (abbrev && strlen(hex) != 40) ? "..." : "";
+ const char *hex = NULL;
+ const char *dots;
+ if (abbrev)
+ hex = find_unique_abbrev(p->object.sha1, abbrev);
+ if (!hex)
+ hex = sha1_to_hex(p->object.sha1);
+ dots = (abbrev && strlen(hex) != 40) ? "..." : "";
parent = parent->next;
offset += sprintf(buf + offset, " %s%s", hex, dots);
# @configure_input@
CC = @CC@
+CFLAGS = @CFLAGS@
AR = @AR@
TAR = @TAR@
#INSTALL = @INSTALL@ # needs install-sh or install.sh in sources
## Checks for programs.
AC_MSG_NOTICE([CHECKS for programs])
#
-AC_PROG_CC
+AC_PROG_CC([cc gcc])
#AC_PROG_INSTALL # needs install-sh or install.sh in sources
AC_CHECK_TOOL(AR, ar, :)
AC_CHECK_PROGS(TAR, [gtar tar])
--- /dev/null
+#
+# bash completion support for core Git.
+#
+# Copyright (C) 2006 Shawn Pearce
+# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
+#
+# The contained completion routines provide support for completing:
+#
+# *) local and remote branch names
+# *) local and remote tag names
+# *) .git/remotes file names
+# *) git 'subcommands'
+# *) tree paths within 'ref:path/to/file' expressions
+#
+# To use these routines:
+#
+# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
+# 2) Added the following line to your .bashrc:
+# source ~/.git-completion.sh
+#
+
+__git_refs ()
+{
+ local cmd i is_hash=y
+ if [ -d "$1" ]; then
+ cmd=git-peek-remote
+ else
+ cmd=git-ls-remote
+ fi
+ for i in $($cmd "$1" 2>/dev/null); do
+ case "$is_hash,$i" in
+ y,*) is_hash=n ;;
+ n,*^{}) is_hash=y ;;
+ n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}" ;;
+ n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}" ;;
+ n,*) is_hash=y; echo "$i" ;;
+ esac
+ done
+}
+
+__git_refs2 ()
+{
+ local cmd i is_hash=y
+ if [ -d "$1" ]; then
+ cmd=git-peek-remote
+ else
+ cmd=git-ls-remote
+ fi
+ for i in $($cmd "$1" 2>/dev/null); do
+ case "$is_hash,$i" in
+ y,*) is_hash=n ;;
+ n,*^{}) is_hash=y ;;
+ n,refs/tags/*) is_hash=y; echo "${i#refs/tags/}:${i#refs/tags/}" ;;
+ n,refs/heads/*) is_hash=y; echo "${i#refs/heads/}:${i#refs/heads/}" ;;
+ n,*) is_hash=y; echo "$i:$i" ;;
+ esac
+ done
+}
+
+__git_remotes ()
+{
+ local i REVERTGLOB=$(shopt -p nullglob)
+ shopt -s nullglob
+ for i in .git/remotes/*; do
+ echo ${i#.git/remotes/}
+ done
+ $REVERTGLOB
+}
+
+__git_complete_file ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ ?*:*)
+ local pfx ls ref="$(echo "$cur" | sed 's,:.*$,,')"
+ cur="$(echo "$cur" | sed 's,^.*:,,')"
+ case "$cur" in
+ ?*/*)
+ pfx="$(echo "$cur" | sed 's,/[^/]*$,,')"
+ cur="$(echo "$cur" | sed 's,^.*/,,')"
+ ls="$ref:$pfx"
+ pfx="$pfx/"
+ ;;
+ *)
+ ls="$ref"
+ ;;
+ esac
+ COMPREPLY=($(compgen -P "$pfx" \
+ -W "$(git-ls-tree "$ls" \
+ | sed '/^100... blob /s,^.* ,,
+ /^040000 tree /{
+ s,^.* ,,
+ s,$,/,
+ }
+ s/^.* //')" \
+ -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ ;;
+ esac
+}
+
+_git_branch ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "-l -f -d -D $(__git_refs .)" -- "$cur"))
+}
+
+_git_cat_file ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-cat-file*,1)
+ COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "-p -t blob tree commit tag" -- "$cur"))
+ ;;
+ *)
+ __git_complete_file
+ ;;
+ esac
+}
+
+_git_checkout ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "-l -b $(__git_refs .)" -- "$cur"))
+}
+
+_git_diff ()
+{
+ __git_complete_file
+}
+
+_git_diff_tree ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "-r -p -M $(__git_refs .)" -- "$cur"))
+}
+
+_git_fetch ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-fetch*,1)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ *)
+ case "$cur" in
+ *:*)
+ cur=$(echo "$cur" | sed 's/^.*://')
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ ;;
+ *)
+ local remote
+ case "${COMP_WORDS[0]}" in
+ git-fetch) remote="${COMP_WORDS[1]}" ;;
+ git) remote="${COMP_WORDS[2]}" ;;
+ esac
+ COMPREPLY=($(compgen -W "$(__git_refs2 "$remote")" -- "$cur"))
+ ;;
+ esac
+ ;;
+ esac
+}
+
+_git_ls_remote ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+}
+
+_git_ls_tree ()
+{
+ __git_complete_file
+}
+
+_git_log ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ case "$cur" in
+ *..*)
+ local pfx=$(echo "$cur" | sed 's/\.\..*$/../')
+ cur=$(echo "$cur" | sed 's/^.*\.\.//')
+ COMPREPLY=($(compgen -P "$pfx" -W "$(__git_refs .)" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+ ;;
+ esac
+}
+
+_git_merge_base ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+}
+
+_git_pull ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-pull*,1)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ *)
+ local remote
+ case "${COMP_WORDS[0]}" in
+ git-pull) remote="${COMP_WORDS[1]}" ;;
+ git) remote="${COMP_WORDS[2]}" ;;
+ esac
+ COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+ ;;
+ esac
+}
+
+_git_push ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+
+ case "${COMP_WORDS[0]},$COMP_CWORD" in
+ git-push*,1)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ git,2)
+ COMPREPLY=($(compgen -W "$(__git_remotes)" -- "$cur"))
+ ;;
+ *)
+ case "$cur" in
+ *:*)
+ local remote
+ case "${COMP_WORDS[0]}" in
+ git-push) remote="${COMP_WORDS[1]}" ;;
+ git) remote="${COMP_WORDS[2]}" ;;
+ esac
+ cur=$(echo "$cur" | sed 's/^.*://')
+ COMPREPLY=($(compgen -W "$(__git_refs "$remote")" -- "$cur"))
+ ;;
+ *)
+ COMPREPLY=($(compgen -W "$(__git_refs2 .)" -- "$cur"))
+ ;;
+ esac
+ ;;
+ esac
+}
+
+_git_show ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "$(__git_refs .)" -- "$cur"))
+}
+
+_git ()
+{
+ if [ $COMP_CWORD = 1 ]; then
+ COMPREPLY=($(compgen \
+ -W "--version $(git help -a|egrep '^ ')" \
+ -- "${COMP_WORDS[COMP_CWORD]}"))
+ else
+ case "${COMP_WORDS[1]}" in
+ branch) _git_branch ;;
+ cat-file) _git_cat_file ;;
+ checkout) _git_checkout ;;
+ diff) _git_diff ;;
+ diff-tree) _git_diff_tree ;;
+ fetch) _git_fetch ;;
+ log) _git_log ;;
+ ls-remote) _git_ls_remote ;;
+ ls-tree) _git_ls_tree ;;
+ pull) _git_pull ;;
+ push) _git_push ;;
+ show) _git_show ;;
+ show-branch) _git_log ;;
+ whatchanged) _git_log ;;
+ *) COMPREPLY=() ;;
+ esac
+ fi
+}
+
+_gitk ()
+{
+ local cur="${COMP_WORDS[COMP_CWORD]}"
+ COMPREPLY=($(compgen -W "--all $(__git_refs .)" -- "$cur"))
+}
+
+complete -o default -o nospace -F _git git
+complete -o default -F _gitk gitk
+complete -o default -F _git_branch git-branch
+complete -o default -o nospace -F _git_cat_file git-cat-file
+complete -o default -F _git_checkout git-checkout
+complete -o default -o nospace -F _git_diff git-diff
+complete -o default -F _git_diff_tree git-diff-tree
+complete -o default -o nospace -F _git_fetch git-fetch
+complete -o default -o nospace -F _git_log git-log
+complete -o default -F _git_ls_remote git-ls-remote
+complete -o default -o nospace -F _git_ls_tree git-ls-tree
+complete -o default -F _git_merge_base git-merge-base
+complete -o default -o nospace -F _git_pull git-pull
+complete -o default -o nospace -F _git_push git-push
+complete -o default -F _git_show git-show
+complete -o default -o nospace -F _git_log git-whatchanged
+
+# The following are necessary only for Cygwin, and only are needed
+# when the user has tab-completed the executable name and consequently
+# included the '.exe' suffix.
+#
+complete -o default -o nospace -F _git_cat_file git-cat-file.exe
+complete -o default -o nospace -F _git_diff git-diff.exe
+complete -o default -o nospace -F _git_diff_tree git-diff-tree.exe
+complete -o default -o nospace -F _git_log git-log.exe
+complete -o default -o nospace -F _git_ls_tree git-ls-tree.exe
+complete -o default -F _git_merge_base git-merge-base.exe
+complete -o default -o nospace -F _git_push git-push.exe
+complete -o default -o nospace -F _git_log git-whatchanged.exe
(propertize
(concat " ("
(if (eq state 'copy) "copied from "
- (if (eq (git-fileinfo->state info) 'added) "renamed to "
- "renamed from "))
+ (if (eq (git-fileinfo->state info) 'added) "renamed from "
+ "renamed to "))
(git-escape-file-name (git-fileinfo->orig-name info))
")") 'face 'git-status-face)
"")))
(defun vc-git-annotate-command (file buf &optional rev)
; FIXME: rev is ignored
(let ((name (file-relative-name file)))
- (call-process "git" nil buf nil "annotate" name)))
+ (call-process "git" nil buf nil "blame" name)))
(defun vc-git-annotate-time ()
- (and (re-search-forward "[0-9a-f]+\t(.*\t\\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\)\t[0-9]+)" nil t)
+ (and (re-search-forward "[0-9a-f]+ (.* \\([0-9]+\\)-\\([0-9]+\\)-\\([0-9]+\\) \\([0-9]+\\):\\([0-9]+\\):\\([0-9]+\\) \\([-+0-9]+\\) +[0-9]+)" nil t)
(vc-annotate-convert-time
(apply #'encode-time (mapcar (lambda (match) (string-to-number (match-string match))) '(6 5 4 3 2 1 7))))))
#include "exec_cmd.h"
#include "interpolate.h"
+#ifndef HOST_NAME_MAX
+#define HOST_NAME_MAX 256
+#endif
+
static int log_syslog;
static int verbose;
static int reuseaddr;
#else /* NO_IPV6 */
-static int socksetup(char *lisen_addr, int listen_port, int **socklist_p)
+static int socksetup(char *listen_addr, int listen_port, int **socklist_p)
{
struct sockaddr_in sin;
int sockfd;
}
if (match_string(date, "PM") == 2) {
- if (tm->tm_hour > 0 && tm->tm_hour < 12)
- tm->tm_hour += 12;
+ tm->tm_hour = (tm->tm_hour % 12) + 12;
+ return 2;
+ }
+
+ if (match_string(date, "AM") == 2) {
+ tm->tm_hour = (tm->tm_hour % 12) + 0;
return 2;
}
date_time(tm, 17);
}
+static void date_pm(struct tm *tm, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12) + 12;
+}
+
+static void date_am(struct tm *tm, int *num)
+{
+ int hour, n = *num;
+ *num = 0;
+
+ hour = tm->tm_hour;
+ if (n) {
+ hour = n;
+ tm->tm_min = 0;
+ tm->tm_sec = 0;
+ }
+ tm->tm_hour = (hour % 12);
+}
+
static const struct special {
const char *name;
void (*fn)(struct tm *, int *);
{ "noon", date_noon },
{ "midnight", date_midnight },
{ "tea", date_tea },
+ { "PM", date_pm },
+ { "AM", date_am },
{ NULL }
};
return end;
}
+static const char *approxidate_digit(const char *date, struct tm *tm, int *num)
+{
+ char *end;
+ unsigned long number = strtoul(date, &end, 10);
+
+ switch (*end) {
+ case ':':
+ case '.':
+ case '/':
+ case '-':
+ if (isdigit(end[1])) {
+ int match = match_multi_number(number, *end, date, end, tm);
+ if (match)
+ return date + match;
+ }
+ }
+
+ *num = number;
+ return end;
+}
+
unsigned long approxidate(const char *date)
{
int number = 0;
break;
date++;
if (isdigit(c)) {
- char *end;
- number = strtoul(date-1, &end, 10);
- date = end;
+ date = approxidate_digit(date-1, &tm, &number);
continue;
}
if (isalpha(c))
continue;
if (ref_size > top - src)
ref_size = top - src;
- if (ref_size > 0xffffff)
- ref_size = 0xffffff;
+ if (ref_size > 0x10000)
+ ref_size = 0x10000;
if (ref_size <= msize)
break;
while (ref_size-- && *src++ == *ref)
/* this is our best match so far */
msize = ref - entry->ptr;
moff = entry->ptr - ref_data;
- if (msize >= 0x10000)
- break; /* this is good enough */
}
}
if (msize & 0xff) { out[outpos++] = msize; i |= 0x10; }
msize >>= 8;
if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
- msize >>= 8;
- if (msize & 0xff) { out[outpos++] = msize; i |= 0x40; }
*op = i;
}
diff_populate_filespec(two, 0);
lc_a = count_lines(one->data, one->size);
lc_b = count_lines(two->data, two->size);
- printf("--- %s\n+++ %s\n@@ -", name_a, name_b);
+ printf("--- a/%s\n+++ b/%s\n@@ -", name_a, name_b);
print_line_count(lc_a);
printf(" +");
print_line_count(lc_b);
x->deleted++;
}
-static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
-static const char minuses[]= "----------------------------------------------------------------------";
const char mime_boundary_leader[] = "------------";
-static void show_stats(struct diffstat_t* data)
+static int scale_linear(int it, int width, int max_change)
+{
+ /*
+ * make sure that at least one '-' is printed if there were deletions,
+ * and likewise for '+'.
+ */
+ if (max_change < 2)
+ return it;
+ return ((it - 1) * (width - 1) + max_change - 1) / (max_change - 1);
+}
+
+static void show_name(const char *prefix, const char *name, int len,
+ const char *reset, const char *set)
+{
+ printf(" %s%s%-*s%s |", set, prefix, len, name, reset);
+}
+
+static void show_graph(char ch, int cnt, const char *set, const char *reset)
+{
+ if (cnt <= 0)
+ return;
+ printf("%s", set);
+ while (cnt--)
+ putchar(ch);
+ printf("%s", reset);
+}
+
+static void show_stats(struct diffstat_t* data, struct diff_options *options)
{
int i, len, add, del, total, adds = 0, dels = 0;
- int max, max_change = 0, max_len = 0;
+ int max_change = 0, max_len = 0;
int total_files = data->nr;
+ int width, name_width;
+ const char *reset, *set, *add_c, *del_c;
if (data->nr == 0)
return;
+ width = options->stat_width ? options->stat_width : 80;
+ name_width = options->stat_name_width ? options->stat_name_width : 50;
+
+ /* Sanity: give at least 5 columns to the graph,
+ * but leave at least 10 columns for the name.
+ */
+ if (width < name_width + 15) {
+ if (name_width <= 25)
+ width = name_width + 15;
+ else
+ name_width = width - 15;
+ }
+
+ /* Find the longest filename and max number of changes */
+ reset = diff_get_color(options->color_diff, DIFF_RESET);
+ set = diff_get_color(options->color_diff, DIFF_PLAIN);
+ add_c = diff_get_color(options->color_diff, DIFF_FILE_NEW);
+ del_c = diff_get_color(options->color_diff, DIFF_FILE_OLD);
+
for (i = 0; i < data->nr; i++) {
struct diffstat_file *file = data->files[i];
+ int change = file->added + file->deleted;
+
+ len = quote_c_style(file->name, NULL, NULL, 0);
+ if (len) {
+ char *qname = xmalloc(len + 1);
+ quote_c_style(file->name, qname, NULL, 0);
+ free(file->name);
+ file->name = qname;
+ }
len = strlen(file->name);
if (max_len < len)
if (file->is_binary || file->is_unmerged)
continue;
- if (max_change < file->added + file->deleted)
- max_change = file->added + file->deleted;
+ if (max_change < change)
+ max_change = change;
}
+ /* Compute the width of the graph part;
+ * 10 is for one blank at the beginning of the line plus
+ * " | count " between the name and the graph.
+ *
+ * From here on, name_width is the width of the name area,
+ * and width is the width of the graph area.
+ */
+ name_width = (name_width < max_len) ? name_width : max_len;
+ if (width < (name_width + 10) + max_change)
+ width = width - (name_width + 10);
+ else
+ width = max_change;
+
for (i = 0; i < data->nr; i++) {
const char *prefix = "";
char *name = data->files[i]->name;
int added = data->files[i]->added;
int deleted = data->files[i]->deleted;
-
- if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
- char *qname = xmalloc(len + 1);
- quote_c_style(name, qname, NULL, 0);
- free(name);
- data->files[i]->name = name = qname;
- }
+ int name_len;
/*
* "scale" the filename
*/
- len = strlen(name);
- max = max_len;
- if (max > 50)
- max = 50;
- if (len > max) {
+ len = name_width;
+ name_len = strlen(name);
+ if (name_width < name_len) {
char *slash;
prefix = "...";
- max -= 3;
- name += len - max;
+ len -= 3;
+ name += name_len - len;
slash = strchr(name, '/');
if (slash)
name = slash;
}
- len = max;
-
- /*
- * scale the add/delete
- */
- max = max_change;
- if (max + len > 70)
- max = 70 - len;
if (data->files[i]->is_binary) {
- printf(" %s%-*s | Bin\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Bin\n");
goto free_diffstat_file;
}
else if (data->files[i]->is_unmerged) {
- printf(" %s%-*s | Unmerged\n", prefix, len, name);
+ show_name(prefix, name, len, reset, set);
+ printf(" Unmerged\n");
goto free_diffstat_file;
}
else if (!data->files[i]->is_renamed &&
goto free_diffstat_file;
}
+ /*
+ * scale the add/delete
+ */
add = added;
del = deleted;
total = add + del;
adds += add;
dels += del;
- if (max_change > 0) {
- total = (total * max + max_change / 2) / max_change;
- add = (add * max + max_change / 2) / max_change;
- del = total - add;
+ if (width <= max_change) {
+ add = scale_linear(add, width, max_change);
+ del = scale_linear(del, width, max_change);
+ total = add + del;
}
- printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
- len, name, added + deleted,
- add, pluses, del, minuses);
+ show_name(prefix, name, len, reset, set);
+ printf("%5d ", added + deleted);
+ show_graph('+', add, add_c, reset);
+ show_graph('-', del, del_c, reset);
+ putchar('\n');
free_diffstat_file:
free(data->files[i]->name);
free(data->files[i]);
}
free(data->files);
- printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
- total_files, adds, dels);
+ printf("%s %d files changed, %d insertions(+), %d deletions(-)%s\n",
+ set, total_files, adds, dels, reset);
+}
+
+static void show_numstat(struct diffstat_t* data, struct diff_options *options)
+{
+ int i;
+
+ for (i = 0; i < data->nr; i++) {
+ struct diffstat_file *file = data->files[i];
+
+ printf("%d\t%d\t", file->added, file->deleted);
+ if (options->line_termination &&
+ quote_c_style(file->name, NULL, NULL, 0))
+ quote_c_style(file->name, NULL, stdout, 0);
+ else
+ fputs(file->name, stdout);
+ putchar(options->line_termination);
+ }
}
struct checkdiff_t {
DIFF_FORMAT_CHECKDIFF |
DIFF_FORMAT_NO_OUTPUT))
options->output_format &= ~(DIFF_FORMAT_RAW |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_PATCH);
* recursive bits for other formats here.
*/
if (options->output_format & (DIFF_FORMAT_PATCH |
+ DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
DIFF_FORMAT_CHECKDIFF))
options->recursive = 1;
else if (!strcmp(arg, "--patch-with-raw")) {
options->output_format |= DIFF_FORMAT_PATCH | DIFF_FORMAT_RAW;
}
- else if (!strcmp(arg, "--stat"))
+ else if (!strcmp(arg, "--numstat")) {
+ options->output_format |= DIFF_FORMAT_NUMSTAT;
+ }
+ else if (!strncmp(arg, "--stat", 6)) {
+ char *end;
+ int width = options->stat_width;
+ int name_width = options->stat_name_width;
+ arg += 6;
+ end = (char *)arg;
+
+ switch (*arg) {
+ case '-':
+ if (!strncmp(arg, "-width=", 7))
+ width = strtoul(arg + 7, &end, 10);
+ else if (!strncmp(arg, "-name-width=", 12))
+ name_width = strtoul(arg + 12, &end, 10);
+ break;
+ case '=':
+ width = strtoul(arg+1, &end, 10);
+ if (*end == ',')
+ name_width = strtoul(end+1, &end, 10);
+ }
+
+ /* Important! This checks all the error cases! */
+ if (*end)
+ return 0;
options->output_format |= DIFF_FORMAT_DIFFSTAT;
+ options->stat_name_width = name_width;
+ options->stat_width = width;
+ }
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
else if (!strcmp(arg, "--summary"))
separator++;
}
- if (output_format & DIFF_FORMAT_DIFFSTAT) {
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_NUMSTAT)) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
- show_stats(&diffstat);
+ if (output_format & DIFF_FORMAT_NUMSTAT)
+ show_numstat(&diffstat, options);
+ if (output_format & DIFF_FORMAT_DIFFSTAT)
+ show_stats(&diffstat, options);
separator++;
}
#define DIFF_FORMAT_RAW 0x0001
#define DIFF_FORMAT_DIFFSTAT 0x0002
-#define DIFF_FORMAT_SUMMARY 0x0004
-#define DIFF_FORMAT_PATCH 0x0008
+#define DIFF_FORMAT_NUMSTAT 0x0004
+#define DIFF_FORMAT_SUMMARY 0x0008
+#define DIFF_FORMAT_PATCH 0x0010
/* These override all above */
-#define DIFF_FORMAT_NAME 0x0010
-#define DIFF_FORMAT_NAME_STATUS 0x0020
-#define DIFF_FORMAT_CHECKDIFF 0x0040
+#define DIFF_FORMAT_NAME 0x0100
+#define DIFF_FORMAT_NAME_STATUS 0x0200
+#define DIFF_FORMAT_CHECKDIFF 0x0400
/* Same as output_format = 0 but we know that -s flag was given
* and we should not give default value to output_format.
*/
-#define DIFF_FORMAT_NO_OUTPUT 0x0080
+#define DIFF_FORMAT_NO_OUTPUT 0x0800
-#define DIFF_FORMAT_CALLBACK 0x0100
+#define DIFF_FORMAT_CALLBACK 0x1000
struct diff_options {
const char *filter;
const char *stat_sep;
long xdl_opts;
+ int stat_width;
+ int stat_name_width;
+
int nr_paths;
const char **paths;
int *pathlens;
" --patch-with-raw\n" \
" output both a patch and the diff-raw format.\n" \
" --stat show diffstat instead of patch.\n" \
+" --numstat show numeric diffstat instead of patch.\n" \
" --patch-with-stat\n" \
" output a patch and prepend its diffstat.\n" \
" --name-only show only names of changed files.\n" \
die("%s: unable to fork off sideband demultiplexer", me);
if (!side_pid) {
/* subprocess */
- char buf[LARGE_PACKET_MAX];
-
close(fd[0]);
if (xd[0] != xd[1])
close(xd[1]);
- if (recv_sideband(me, xd[0], fd[1], 2, buf, sizeof(buf)))
+ if (recv_sideband(me, xd[0], fd[1], 2))
exit(1);
exit(0);
}
*)
usage ;;
esac
- git checkout "$branch" &&
- rm -fr "$GIT_DIR/refs/bisect"
- rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
- rm -f "$GIT_DIR/BISECT_LOG"
- rm -f "$GIT_DIR/BISECT_NAMES"
+ if git checkout "$branch"; then
+ rm -fr "$GIT_DIR/refs/bisect"
+ rm -f "$GIT_DIR/refs/heads/bisect" "$GIT_DIR/head-name"
+ rm -f "$GIT_DIR/BISECT_LOG"
+ rm -f "$GIT_DIR/BISECT_NAMES"
+ fi
}
bisect_replay () {
cd "$2" &&
clone_tmp="$GIT_DIR/clone-tmp" &&
mkdir -p "$clone_tmp" || exit 1
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git-repo-config --bool http.noEPSV`" = true ]; then
+ curl_extra_args="${curl_extra_args} --disable-epsv"
+ fi
http_fetch "$1/info/refs" "$clone_tmp/refs" || {
echo >&2 "Cannot get remote repository information.
Perhaps git-update-server-info needs to be run there?"
fi
;;
*)
- cd "$D" && case "$upload_pack" in
+ case "$upload_pack" in
'') git-fetch-pack --all -k $quiet "$repo" ;;
*) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;;
esac >"$GIT_DIR/CLONE_HEAD" || {
cp -p "$THIS_INDEX" "$NEXT_INDEX"
}
-report () {
- header="#
-# $1:
-# ($2)
-#
-"
- trailer=""
- while read status name newname
- do
- printf '%s' "$header"
- header=""
- trailer="#
-"
- case "$status" in
- M ) echo "# modified: $name";;
- D*) echo "# deleted: $name";;
- T ) echo "# typechange: $name";;
- C*) echo "# copied: $name -> $newname";;
- R*) echo "# renamed: $name -> $newname";;
- A*) echo "# new file: $name";;
- U ) echo "# unmerged: $name";;
- esac
- done
- printf '%s' "$trailer"
- [ "$header" ]
-}
-
run_status () {
# If TMP_INDEX is defined, that means we are doing
# "--only" partial commit, and that index file is used
# so the regular index file is what we use to compare.
if test '' != "$TMP_INDEX"
then
- GIT_INDEX_FILE="$TMP_INDEX"
- export GIT_INDEX_FILE
+ GIT_INDEX_FILE="$TMP_INDEX"
+ export GIT_INDEX_FILE
elif test -f "$NEXT_INDEX"
then
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
+ GIT_INDEX_FILE="$NEXT_INDEX"
+ export GIT_INDEX_FILE
fi
- case "$status_only" in
- t) color= ;;
- *) color=--nocolor ;;
- esac
- git-runstatus ${color} \
- ${verbose:+--verbose} \
- ${amend:+--amend} \
+ case "$status_only" in
+ t) color= ;;
+ *) color=--nocolor ;;
+ esac
+ git-runstatus ${color} \
+ ${verbose:+--verbose} \
+ ${amend:+--amend} \
${untracked_files:+--untracked}
}
untracked_files=
while case "$#" in 0) break;; esac
do
- case "$1" in
- -F|--F|-f|--f|--fi|--fil|--file)
- case "$#" in 1) usage ;; esac
- shift
- no_edit=t
- log_given=t$log_given
- logfile="$1"
- shift
- ;;
- -F*|-f*)
- no_edit=t
- log_given=t$log_given
- logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
- shift
- ;;
- --F=*|--f=*|--fi=*|--fil=*|--file=*)
- no_edit=t
- log_given=t$log_given
- logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- shift
- ;;
- -a|--a|--al|--all)
- all=t
- shift
- ;;
- --au=*|--aut=*|--auth=*|--autho=*|--author=*)
- force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- shift
- ;;
- --au|--aut|--auth|--autho|--author)
- case "$#" in 1) usage ;; esac
- shift
- force_author="$1"
- shift
- ;;
- -e|--e|--ed|--edi|--edit)
- edit_flag=t
- shift
- ;;
- -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
- also=t
- shift
- ;;
- -o|--o|--on|--onl|--only)
- only=t
- shift
- ;;
- -m|--m|--me|--mes|--mess|--messa|--messag|--message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message="$1"
- else
- log_message="$log_message
+ case "$1" in
+ -F|--F|-f|--f|--fi|--fil|--file)
+ case "$#" in 1) usage ;; esac
+ shift
+ no_edit=t
+ log_given=t$log_given
+ logfile="$1"
+ shift
+ ;;
+ -F*|-f*)
+ no_edit=t
+ log_given=t$log_given
+ logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
+ shift
+ ;;
+ --F=*|--f=*|--fi=*|--fil=*|--file=*)
+ no_edit=t
+ log_given=t$log_given
+ logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ shift
+ ;;
+ -a|--a|--al|--all)
+ all=t
+ shift
+ ;;
+ --au=*|--aut=*|--auth=*|--autho=*|--author=*)
+ force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ shift
+ ;;
+ --au|--aut|--auth|--autho|--author)
+ case "$#" in 1) usage ;; esac
+ shift
+ force_author="$1"
+ shift
+ ;;
+ -e|--e|--ed|--edi|--edit)
+ edit_flag=t
+ shift
+ ;;
+ -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
+ also=t
+ shift
+ ;;
+ -o|--o|--on|--onl|--only)
+ only=t
+ shift
+ ;;
+ -m|--m|--me|--mes|--mess|--messa|--messag|--message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message="$1"
+ else
+ log_message="$log_message
$1"
- fi
- no_edit=t
- shift
- ;;
- -m*)
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message=`expr "z$1" : 'z-m\(.*\)'`
- else
- log_message="$log_message
+ fi
+ no_edit=t
+ shift
+ ;;
+ -m*)
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message=`expr "z$1" : 'z-m\(.*\)'`
+ else
+ log_message="$log_message
`expr "z$1" : 'z-m\(.*\)'`"
- fi
- no_edit=t
- shift
- ;;
- --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
- log_given=m$log_given
- if test "$log_message" = ''
- then
- log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- else
- log_message="$log_message
+ fi
+ no_edit=t
+ shift
+ ;;
+ --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
+ log_given=m$log_given
+ if test "$log_message" = ''
+ then
+ log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ else
+ log_message="$log_message
`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
- fi
- no_edit=t
- shift
- ;;
- -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|--no-verify)
- verify=
- shift
- ;;
- --a|--am|--ame|--amen|--amend)
- amend=t
- log_given=t$log_given
- use_commit=HEAD
- shift
- ;;
- -c)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- shift
- ;;
- --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
- --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
- --reedit-messag=*|--reedit-message=*)
- log_given=t$log_given
- use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- no_edit=
- shift
- ;;
- --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
- --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|--reedit-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=
- shift
- ;;
- -C)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- shift
- ;;
- --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
- --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
- --reuse-message=*)
- log_given=t$log_given
- use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
- no_edit=t
- shift
- ;;
- --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
- --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
- case "$#" in 1) usage ;; esac
- shift
- log_given=t$log_given
- use_commit="$1"
- no_edit=t
- shift
- ;;
- -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
- signoff=t
- shift
- ;;
- -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
- verbose=t
- shift
- ;;
- -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|--untracked|\
- --untracked-|--untracked-f|--untracked-fi|--untracked-fil|--untracked-file|\
- --untracked-files)
- untracked_files=t
- shift
- ;;
- --)
- shift
- break
- ;;
- -*)
- usage
- ;;
- *)
- break
- ;;
- esac
+ fi
+ no_edit=t
+ shift
+ ;;
+ -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
+ --no-verify)
+ verify=
+ shift
+ ;;
+ --a|--am|--ame|--amen|--amend)
+ amend=t
+ log_given=t$log_given
+ use_commit=HEAD
+ shift
+ ;;
+ -c)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=
+ shift
+ ;;
+ --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
+ --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
+ --reedit-messag=*|--reedit-message=*)
+ log_given=t$log_given
+ use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ no_edit=
+ shift
+ ;;
+ --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
+ --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
+ --reedit-message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=
+ shift
+ ;;
+ -C)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift
+ ;;
+ --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
+ --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
+ --reuse-message=*)
+ log_given=t$log_given
+ use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+ no_edit=t
+ shift
+ ;;
+ --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
+ --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
+ case "$#" in 1) usage ;; esac
+ shift
+ log_given=t$log_given
+ use_commit="$1"
+ no_edit=t
+ shift
+ ;;
+ -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
+ signoff=t
+ shift
+ ;;
+ -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+ verbose=t
+ shift
+ ;;
+ -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
+ --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
+ --untracked-file|--untracked-files)
+ untracked_files=t
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
done
case "$edit_flag" in t) no_edit= ;; esac
case "$amend,$initial_commit" in
t,t)
- die "You do not have anything to amend." ;;
+ die "You do not have anything to amend." ;;
t,)
- if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
- die "You are in the middle of a merge -- cannot amend."
- fi ;;
+ if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
+ die "You are in the middle of a merge -- cannot amend."
+ fi ;;
esac
case "$log_given" in
tt*)
- die "Only one of -c/-C/-F can be used." ;;
+ die "Only one of -c/-C/-F can be used." ;;
*tm*|*mt*)
- die "Option -m cannot be combined with -c/-C/-F." ;;
+ die "Option -m cannot be combined with -c/-C/-F." ;;
esac
case "$#,$also,$only,$amend" in
*,t,t,*)
- die "Only one of --include/--only can be used." ;;
+ die "Only one of --include/--only can be used." ;;
0,t,,* | 0,,t,)
- die "No paths with --include/--only does not make sense." ;;
+ die "No paths with --include/--only does not make sense." ;;
0,,t,t)
- only_include_assumed="# Clever... amending the last one with dirty index." ;;
+ only_include_assumed="# Clever... amending the last one with dirty index." ;;
0,,,*)
- ;;
+ ;;
*,,,*)
- only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
- also=
- ;;
+ only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+ also=
+ ;;
esac
unset only
case "$all,$also,$#" in
,)
case "$#" in
0)
- ;; # commit as-is
+ ;; # commit as-is
*)
- if test -f "$GIT_DIR/MERGE_HEAD"
- then
- refuse_partial "Cannot do a partial commit during a merge."
- fi
- TMP_INDEX="$GIT_DIR/tmp-index$$"
- if test -z "$initial_commit"
- then
- # make sure index is clean at the specified paths, or
- # they are additions.
- dirty_in_index=`git-diff-index --cached --name-status \
- --diff-filter=DMTU HEAD -- "$@"`
- test -z "$dirty_in_index" ||
- refuse_partial "Different in index and the last commit:
+ if test -f "$GIT_DIR/MERGE_HEAD"
+ then
+ refuse_partial "Cannot do a partial commit during a merge."
+ fi
+ TMP_INDEX="$GIT_DIR/tmp-index$$"
+ if test -z "$initial_commit"
+ then
+ # make sure index is clean at the specified paths, or
+ # they are additions.
+ dirty_in_index=`git-diff-index --cached --name-status \
+ --diff-filter=DMTU HEAD -- "$@"`
+ test -z "$dirty_in_index" ||
+ refuse_partial "Different in index and the last commit:
$dirty_in_index"
- fi
- commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
-
- # Build the temporary index and update the real index
- # the same way.
- if test -z "$initial_commit"
- then
- cp "$THIS_INDEX" "$TMP_INDEX"
- GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD
- else
- rm -f "$TMP_INDEX"
- fi || exit
-
- echo "$commit_only" |
- GIT_INDEX_FILE="$TMP_INDEX" \
- git-update-index --add --remove --stdin &&
-
- save_index &&
- echo "$commit_only" |
- (
- GIT_INDEX_FILE="$NEXT_INDEX"
- export GIT_INDEX_FILE
- git-update-index --remove --stdin
- ) || exit
- ;;
+ fi
+ commit_only=`git-ls-files --error-unmatch -- "$@"` || exit
+
+ # Build the temporary index and update the real index
+ # the same way.
+ if test -z "$initial_commit"
+ then
+ cp "$THIS_INDEX" "$TMP_INDEX"
+ GIT_INDEX_FILE="$TMP_INDEX" git-read-tree -m HEAD
+ else
+ rm -f "$TMP_INDEX"
+ fi || exit
+
+ echo "$commit_only" |
+ GIT_INDEX_FILE="$TMP_INDEX" \
+ git-update-index --add --remove --stdin &&
+
+ save_index &&
+ echo "$commit_only" |
+ (
+ GIT_INDEX_FILE="$NEXT_INDEX"
+ export GIT_INDEX_FILE
+ git-update-index --remove --stdin
+ ) || exit
+ ;;
esac
;;
esac
fi
GIT_INDEX_FILE="$USE_INDEX" \
- git-update-index -q $unmerged_ok_if_status --refresh || exit
+ git-update-index -q $unmerged_ok_if_status --refresh || exit
################################################################
# If the request is status, just show it and exit.
if ($fields[4] eq 'M') {
push @mfiles, $fields[5];
}
- if ($fields[4] eq 'R') {
+ if ($fields[4] eq 'D') {
push @dfiles, $fields[5];
}
}
$state->{directory} = "" if ( $state->{directory} eq "." );
$state->{directory} .= "/" if ( $state->{directory} =~ /\S/ );
- if ( not defined($state->{prependdir}) and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
+ if ( (not defined($state->{prependdir}) or $state->{prependdir} eq '') and $state->{localdir} eq "." and $state->{path} =~ /\S/ )
{
$log->info("Setting prepend to '$state->{path}'");
$state->{prependdir} = $state->{path};
$meta = $updater->getmeta($filename);
}
- next unless ( $meta->{revision} );
+ if ( ! defined $meta )
+ {
+ $meta = {
+ name => $filename,
+ revision => 0,
+ filehash => 'added'
+ };
+ }
my $oldmeta = $meta;
and not exists ( $state->{opt}{C} ) )
{
$log->info("Tell the client the file is modified");
- print "MT text U\n";
+ print "MT text M \n";
print "MT fname $filename\n";
print "MT newline\n";
next;
}
}
elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
- or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+ or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash}
+ or $meta->{filehash} eq 'added' )
{
- $log->info("Updating '$filename'");
- # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
- print "MT +updated\n";
- print "MT text U\n";
- print "MT fname $filename\n";
- print "MT newline\n";
- print "MT -updated\n";
+ # normal update, just send the new revision (either U=Update,
+ # or A=Add, or R=Remove)
+ if ( defined($wrev) && $wrev < 0 )
+ {
+ $log->info("Tell the client the file is scheduled for removal");
+ print "MT text R \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
+ }
+ elsif ( !defined($wrev) || $wrev == 0 )
+ {
+ $log->info("Tell the client the file will be added");
+ print "MT text A \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
+
+ }
+ else {
+ $log->info("Updating '$filename' $wrev");
+ print "MT +updated\n";
+ print "MT text U \n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ print "MT -updated\n";
+ }
my ( $filepart, $dirpart ) = filenamesplit($filename,1);
return if ( scalar ( @{$state->{args}} ) > 1 );
+ my @gethead = @{$updater->gethead};
+
+ # push added files
+ foreach my $file (keys %{$state->{entries}}) {
+ if ( exists $state->{entries}{$file}{revision} &&
+ $state->{entries}{$file}{revision} == 0 )
+ {
+ push @gethead, { name => $file, filehash => 'added' };
+ }
+ }
+
if ( scalar(@{$state->{args}}) == 1 )
{
my $arg = $state->{args}[0];
$log->info("Only one arg specified, checking for directory expansion on '$arg'");
- foreach my $file ( @{$updater->gethead} )
+ foreach my $file ( @gethead )
{
next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
next unless ( $file->{name} =~ /^$arg\// or $file->{name} eq $arg );
$state->{args} = [];
- foreach my $file ( @{$updater->gethead} )
+ foreach my $file ( @gethead )
{
next if ( $file->{filehash} eq "deleted" and not defined ( $state->{entries}{$file->{name}} ) );
next unless ( $file->{name} =~ s/^$state->{prependdir}// );
then
headc_=$(git-rev-parse --verify "$head_^0") || exit
echo "$headc_ $not_for_merge_ $note_" >>"$GIT_DIR/FETCH_HEAD"
- [ "$verbose" ] && echo >&2 "* committish: $head_"
- [ "$verbose" ] && echo >&2 " $note_"
else
echo "$head_ not-for-merge $note_" >>"$GIT_DIR/FETCH_HEAD"
- [ "$verbose" ] && echo >&2 "* non-commit: $head_"
- [ "$verbose" ] && echo >&2 " $note_"
- fi
- if test "$local_name_" != ""
- then
- # We are storing the head locally. Make sure that it is
- # a fast forward (aka "reverse push").
- fast_forward_local "$local_name_" "$head_" "$note_"
fi
+
+ update_local_ref "$local_name_" "$head_" "$note_"
}
-fast_forward_local () {
+update_local_ref () {
+ # If we are storing the head locally make sure that it is
+ # a fast forward (aka "reverse push").
+
+ label_=$(git-cat-file -t $2)
+ newshort_=$(git-rev-parse --short $2)
+ if test -z "$1" ; then
+ [ "$verbose" ] && echo >&2 "* fetched $3"
+ [ "$verbose" ] && echo >&2 " $label_: $newshort_"
+ return 0
+ fi
+ oldshort_=$(git-rev-parse --short "$1" 2>/dev/null)
mkdir -p "$(dirname "$GIT_DIR/$1")"
case "$1" in
refs/tags/*)
then
if now_=$(cat "$GIT_DIR/$1") && test "$now_" = "$2"
then
- [ "$verbose" ] && echo >&2 "* $1: same as $3" ||:
+ [ "$verbose" ] && echo >&2 "* $1: same as $3"
+ [ "$verbose" ] && echo >&2 " $label_: $newshort_" ||:
else
echo >&2 "* $1: updating with $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: updating tag" "$1" "$2"
fi
else
echo >&2 "* $1: storing $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: storing tag" "$1" "$2"
fi
;;
if test -n "$verbose"
then
echo >&2 "* $1: same as $3"
+ echo >&2 " $label_: $newshort_"
fi
;;
*,$local)
echo >&2 "* $1: fast forward to $3"
- echo >&2 " from $local to $2"
+ echo >&2 " old..new: $oldshort_..$newshort_"
git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local"
;;
*)
false
;;
esac || {
- echo >&2 "* $1: does not fast forward to $3;"
case ",$force,$single_force," in
*,t,*)
- echo >&2 " forcing update."
+ echo >&2 "* $1: forcing update to non-fast forward $3"
+ echo >&2 " old...new: $oldshort_...$newshort_"
git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local"
;;
*)
- echo >&2 " not updating."
+ echo >&2 "* $1: not updating to non-fast forward $3"
+ echo >&2 " old...new: $oldshort_...$newshort_"
exit 1
;;
esac
}
else
echo >&2 "* $1: storing $3"
+ echo >&2 " $label_: $newshort_"
git-update-ref -m "$rloga: storing head" "$1" "$2"
fi
;;
fetch_main () {
reflist="$1"
refs=
+ rref=
for ref in $reflist
do
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git-repo-config --bool http.noEPSV`" = true ]; then
+ noepsv_opt="--disable-epsv"
+ fi
max_depth=5
depth=0
head="ref: $remote_name"
$u =~ s{([^-a-zA-Z0-9/.])}{sprintf"%%%02x",ord($1)}eg;
print "$u";
' "$head")
- head=$(curl -nsfL $curl_extra_args "$remote/$remote_name_quoted")
+ head=$(curl -nsfL $curl_extra_args $noepsv_opt "$remote/$remote_name_quoted")
depth=$( expr \( $depth + 1 \) )
done
expr "z$head" : "z$_x40\$" >/dev/null ||
# If the original head was empty (i.e. no "master" yet), or
# if we were told not to worry, we do not have to check.
-case ",$update_head_ok,$orig_head," in
-*,, | t,* )
+case "$orig_head" in
+'')
;;
-*)
+?*)
curr_head=$(git-rev-parse --verify HEAD 2>/dev/null)
if test "$curr_head" != "$orig_head"
then
if [ -n "$GIT_SSL_NO_VERIFY" ]; then
curl_extra_args="-k"
fi
+ if [ -n "$GIT_CURL_FTP_NO_EPSV" -o \
+ "`git-repo-config --bool http.noEPSV`" = true ]; then
+ curl_extra_args="${curl_extra_args} --disable-epsv"
+ fi
curl -nsf $curl_extra_args --header "Pragma: no-cache" "$peek_repo/info/refs" ||
echo "failed slurping"
;;
;;
?,1,"$head",*)
# Again the most common case of merging one remote.
- echo "Updating from $head to $1"
+ echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $1)"
git-update-index --refresh 2>/dev/null
new_head=$(git-rev-parse --verify "$1^0") &&
git-read-tree -u -v -m $head "$new_head" &&
echo >&2 "Warning: fetch updated the current branch head."
echo >&2 "Warning: fast forwarding your working tree from"
- echo >&2 "Warning: $orig_head commit."
+ echo >&2 "Warning: commit $orig_head."
git-update-index --refresh 2>/dev/null
git-read-tree -u -m "$orig_head" "$curr_head" ||
die 'Cannot fast-forward your working tree.
# Copyright (c) 2005 Junio C Hamano.
#
-USAGE='[--onto <newbase>] <upstream> [<branch>]'
+USAGE='[-v] [--onto <newbase>] <upstream> [<branch>]'
LONG_USAGE='git-rebase replaces <branch> with a new branch of the
same name. When the --onto option is provided the new branch starts
out with a HEAD equal to <newbase>, otherwise it is equal to <upstream>
do_merge=
dotest=$GIT_DIR/.dotest-merge
prec=4
+verbose=
continue_merge () {
test -n "$prev_head" || die "prev_head must be defined"
esac
do_merge=t
;;
+ -v|--verbose)
+ verbose=t
+ ;;
-*)
usage
;;
exit 0
fi
+if test -n "$verbose"
+then
+ echo "Changes from $mb to $onto:"
+ git-diff-tree --stat --summary "$mb" "$onto"
+fi
+
# Rewind the head to "$onto"; this saves our current head in ORIG_HEAD.
git-reset --hard "$onto"
if test -z "$do_merge"
then
- git-format-patch -k --stdout --full-index "$upstream"..ORIG_HEAD |
+ git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD |
git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \
--reflog-action=rebase
exit $?
exit 0
;;
"$head")
- echo "Updating from $head to $merge"
+ echo "Updating $(git-rev-parse --short $head)..$(git-rev-parse --short $merge)"
git-read-tree -u -m $head $merge || exit 1
git-update-ref -m "resolve $merge_name: Fast forward" \
HEAD "$merge" "$head"
case "$0" in
*-revert* )
test -t 0 && edit=-e
+ replay=
me=revert
USAGE='[--edit | --no-edit] [-n] <commit-ish>' ;;
*-cherry-pick* )
+ replay=t
edit=
me=cherry-pick
- USAGE='[--edit] [-n] [-r] <commit-ish>' ;;
+ USAGE='[--edit] [-n] [-r] [-x] <commit-ish>' ;;
* )
die "What are you talking about?" ;;
esac
. git-sh-setup
-no_commit= replay=
+no_commit=
while case "$#" in 0) break ;; esac
do
case "$1" in
--n|--no|--no-|--no-e|--no-ed|--no-edi|--no-edit)
edit=
;;
- -r|--r|--re|--rep|--repl|--repla|--replay)
- replay=t
+ -r)
+ : no-op ;;
+ -x|--i-really-want-to-expose-my-private-commit-object-name)
+ replay=
;;
-*)
usage
git-cat-file commit $commit | sed -e '1,/^$/d'
case "$replay" in
'')
- echo "(cherry picked from $commit commit)"
+ echo "(cherry picked from commit $commit)"
test "$rev" = "$commit" ||
echo "(original 'git cherry-pick' arguments: $@)"
;;
use Term::ReadLine;
use Getopt::Long;
use Data::Dumper;
+use Git;
package FakeTerm;
sub new {
$initial_reply_to,$initial_subject,@files,$from,$compose,$time);
# Behavior modification variables
-my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc) = (1, 0, 0, 0);
+my ($chain_reply_to, $quiet, $suppress_from, $no_signed_off_cc,
+ $dry_run) = (1, 0, 0, 0, 0);
my $smtp_server;
# Example reply to:
#$initial_reply_to = ''; #<20050203173208.GA23964@foobar.com>';
+my $repo = Git->repository();
my $term = eval {
new Term::ReadLine 'git-send-email';
};
"quiet" => \$quiet,
"suppress-from" => \$suppress_from,
"no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
+ "dry-run" => \$dry_run,
);
# Verify the user input
# Now, let's fill any that aren't set in with defaults:
-sub gitvar {
- my ($var) = @_;
- my $fh;
- my $pid = open($fh, '-|');
- die "$!" unless defined $pid;
- if (!$pid) {
- exec('git-var', $var) or die "$!";
- }
- my ($val) = <$fh>;
- close $fh or die "$!";
- chomp($val);
- return $val;
-}
-
-sub gitvar_ident {
- my ($name) = @_;
- my $val = gitvar($name);
- my @field = split(/\s+/, $val);
- return join(' ', @field[0...(@field-3)]);
-}
-
-my ($author) = gitvar_ident('GIT_AUTHOR_IDENT');
-my ($committer) = gitvar_ident('GIT_COMMITTER_IDENT');
+my ($author) = $repo->ident_person('author');
+my ($committer) = $repo->ident_person('committer');
my %aliases;
-chomp(my @alias_files = `git-repo-config --get-all sendemail.aliasesfile`);
-chomp(my $aliasfiletype = `git-repo-config sendemail.aliasfiletype`);
+my @alias_files = $repo->config('sendemail.aliasesfile');
+my $aliasfiletype = $repo->config('sendemail.aliasfiletype');
my %parse_alias = (
# multiline formats can be supported in the future
mutt => sub { my $fh = shift; while (<$fh>) {
}}}
);
-if (@alias_files && defined $parse_alias{$aliasfiletype}) {
+if (@alias_files and $aliasfiletype and defined $parse_alias{$aliasfiletype}) {
foreach my $file (@alias_files) {
open my $fh, '<', $file or die "opening $file: $!\n";
$parse_alias{$aliasfiletype}->($fh);
my $date = format_2822_time($time++);
my $gitversion = '@@GIT_VERSION@@';
if ($gitversion =~ m/..GIT_VERSION../) {
- $gitversion = `git --version`;
- chomp $gitversion;
- # keep only what's after the last space
- $gitversion =~ s/^.* //;
+ $gitversion = Git::version();
}
my $header = "From: $from
$header .= "References: $references\n";
}
- if ($smtp_server =~ m#^/#) {
+ if ($dry_run) {
+ # We don't want to send the email.
+ } elsif ($smtp_server =~ m#^/#) {
my $pid = open my $sm, '|-';
defined $pid or die $!;
if (!$pid) {
send_message();
# set up for the next message
- if ($chain_reply_to || length($reply_to) == 0) {
+ if ($chain_reply_to || !defined $reply_to || length($reply_to) == 0) {
$reply_to = $message_id;
if (length $references > 0) {
$references .= " $message_id";
#!/usr/bin/perl -w
use strict;
+use Getopt::Std;
+use File::Basename qw(basename dirname);
+
+our ($opt_h, $opt_n, $opt_s);
+getopts('hns');
+
+$opt_h && usage();
+
+sub usage {
+ print STDERR "Usage: ${\basename $0} [-h] [-n] [-s] < <log_data>\n";
+ exit(1);
+}
my (%mailmap);
my (%email);
uc($a) cmp uc($b);
}
+sub by_nbentries($$) {
+ my ($a, $b) = @_;
+ my $a_entries = $map{$a};
+ my $b_entries = $map{$b};
+
+ @$b_entries - @$a_entries || by_name $a, $b;
+}
+
+my $sort_method = $opt_n ? \&by_nbentries : \&by_name;
+
+sub summary_output {
+ my ($obj, $num, $key);
+
+ foreach $key (sort $sort_method keys %map) {
+ $obj = $map{$key};
+ $num = @$obj;
+ printf "%s: %u\n", $key, $num;
+ $n_output += $num;
+ }
+}
sub shortlog_output {
- my ($obj, $key, $desc);
+ my ($obj, $num, $key, $desc);
+
+ foreach $key (sort $sort_method keys %map) {
+ $obj = $map{$key};
+ $num = @$obj;
- foreach $key (sort by_name keys %map) {
# output author
- printf "%s:\n", $key;
+ printf "%s (%u):\n", $key, $num;
# output author's 1-line summaries
- $obj = $map{$key};
foreach $desc (reverse @$obj) {
print " $desc\n";
$n_output++;
&setup_mailmap;
&changelog_input;
-&shortlog_output;
+$opt_s ? &summary_output : &shortlog_output;
&finalize;
exit(0);
memoize('get_commit_time');
my ($SVN_PATH, $SVN, $SVN_LOG, $_use_lib);
+
+sub nag_lib {
+ print STDERR <<EOF;
+! Please consider installing the SVN Perl libraries (version 1.1.0 or
+! newer). You will generally get better performance and fewer bugs,
+! especially if you:
+! 1) have a case-insensitive filesystem
+! 2) replace symlinks with files (and vice-versa) in commits
+
+EOF
+}
+
$_use_lib = 1 unless $ENV{GIT_SVN_NO_LIB};
libsvn_load();
+nag_lib() unless $_use_lib;
+
my $_optimize_commits = 1 unless $ENV{GIT_SVN_NO_OPTIMIZE_COMMITS};
my $sha1 = qr/[a-f\d]{40}/;
my $sha1_short = qr/[a-f\d]{4,40}/;
$_template, $_shared, $_no_default_regex, $_no_graft_copy,
$_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit,
$_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m,
- $_merge, $_strategy, $_dry_run, $_ignore_nodate);
+ $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive);
my (@_branch_from, %tree_map, %users, %rusers, %equiv);
my ($_svn_co_url_revs, $_svn_pg_peg_revs);
my @repo_path_split_cache;
'incremental' => \$_incremental,
'oneline' => \$_oneline,
'show-commit' => \$_show_commit,
+ 'non-recursive' => \$_non_recursive,
'authors-file|A=s' => \$_authors,
} ],
'commit-diff' => [ \&commit_diff, 'Commit a diff between two trees',
foreach (sort keys %cmd) {
next if $cmd && $cmd ne $_;
- print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n";
+ print $fd ' ',pack('A17',$_),$cmd{$_}->[1],"\n";
foreach (keys %{$cmd{$_}->[2]}) {
# prints out arguments as they should be passed:
my $x = s#[:=]s$## ? '<arg>' : s#[:=]i$## ? '<num>' : '';
- print $fd ' ' x 17, join(', ', map { length $_ > 1 ?
+ print $fd ' ' x 21, join(', ', map { length $_ > 1 ?
"--$_" : "-$_" }
split /\|/,$_)," $x\n";
}
$SVN = libsvn_connect($repo);
my $ed = SVN::Git::Editor->new(
{ r => $r_last,
- ra => $SVN,
+ ra => $SVN_LOG,
c => $c,
svn_path => $SVN_PATH
},
}
$_trunk = $url . $_trunk;
}
+ my $ch_id;
if ($GIT_SVN eq 'git-svn') {
- print "GIT_SVN_ID set to 'trunk' for $_trunk\n";
+ $ch_id = 1;
$GIT_SVN = $ENV{GIT_SVN_ID} = 'trunk';
}
init_vars();
- init($_trunk);
+ unless (-d $GIT_SVN_DIR) {
+ print "GIT_SVN_ID set to 'trunk' for $_trunk\n" if $ch_id;
+ init($_trunk);
+ sys('git-repo-config', 'svn.trunk', $_trunk);
+ }
complete_url_ls_init($url, $_branches, '--branches/-b', '');
complete_url_ls_init($url, $_tags, '--tags/-t', 'tags/');
}
# ignore
} elsif (/^:\d{6} \d{6} $sha1_short/o) {
push @{$c->{raw}}, $_;
+ } elsif (/^[ACRMDT]\t/) {
+ # we could add $SVN_PATH here, but that requires
+ # remote access at the moment (repo_path_split)...
+ s#^([ACRMDT])\t# $1 #;
+ push @{$c->{changed}}, $_;
} elsif (/^diff /) {
$d = 1;
push @{$c->{diff}}, $_;
} elsif ($d) {
push @{$c->{diff}}, $_;
} elsif (/^ (git-svn-id:.+)$/) {
- (undef, $c->{r}, undef) = extract_metadata($1);
+ ($c->{url}, $c->{r}, undef) = extract_metadata($1);
} elsif (s/^ //) {
push @{$c->{l}}, $_;
}
$SVN ||= libsvn_connect($repo);
my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef, 0) : ();
my $ed = SVN::Git::Editor->new({ r => $SVN->get_latest_revnum,
- ra => $SVN, c => $tb,
+ ra => $SVN_LOG, c => $tb,
svn_path => $SVN_PATH
},
$SVN->get_commit_editor($_message,
my ($r_min, $r_max) = @_;
my @cmd = (qw/git-log --abbrev-commit --pretty=raw
--default/, "refs/remotes/$GIT_SVN");
- push @cmd, '--summary' if $_verbose;
+ push @cmd, '-r' unless $_non_recursive;
+ push @cmd, qw/--raw --name-status/ if $_verbose;
return @cmd unless defined $r_max;
if ($r_max == $r_min) {
push @cmd, '--max-count=1';
my ($c_min, $c_max);
$c_max = revdb_get($REVDB, $r_max);
$c_min = revdb_get($REVDB, $r_min);
- if ($c_min && $c_max) {
+ if (defined $c_min && defined $c_max) {
if ($r_max > $r_max) {
push @cmd, "$c_min..$c_max";
} else {
print STDERR "W: Unrecognized URL: $u\n";
die "This should never happen\n";
}
+ # don't try to init already existing refs
my $id = $pfx.$1;
- print "init $u => $id\n";
$GIT_SVN = $ENV{GIT_SVN_ID} = $id;
init_vars();
- init($u);
+ unless (-d $GIT_SVN_DIR) {
+ print "init $u => $id\n";
+ init($u);
+ }
}
exit 0;
}
waitpid $pid, 0;
croak $? if $?;
+ my ($n) = ($switch =~ /^--(\w+)/);
+ sys('git-repo-config', "svn.$n", $var);
}
sub common_prefix {
}
}
+sub show_commit_changed_paths {
+ my ($c) = @_;
+ return unless $c->{changed};
+ print "Changed paths:\n", @{$c->{changed}};
+}
+
sub show_commit_normal {
my ($c) = @_;
print '-' x72, "\nr$c->{r} | ";
my $nr_line = 0;
if (my $l = $c->{l}) {
- while ($l->[$#$l] eq "\n" && $l->[($#$l - 1)] eq "\n") {
+ while ($l->[$#$l] eq "\n" && $#$l > 0
+ && $l->[($#$l - 1)] eq "\n") {
pop @$l;
}
$nr_line = scalar @$l;
} else {
$nr_line .= ' lines';
}
- print $nr_line, "\n\n";
+ print $nr_line, "\n";
+ show_commit_changed_paths($c);
+ print "\n";
print $_ foreach @$l;
}
} else {
- print "1 line\n\n";
+ print "1 line\n";
+ show_commit_changed_paths($c);
+ print "\n";
}
foreach my $x (qw/raw diff/) {
seek $fh, 0, 0 or croak $!;
my $exp = $md5->hexdigest;
- my $atd = $self->apply_textdelta($fbat, undef, $self->{pool});
- my $got = SVN::TxDelta::send_stream($fh, @$atd, $self->{pool});
+ my $pool = SVN::Pool->new;
+ my $atd = $self->apply_textdelta($fbat, undef, $pool);
+ my $got = SVN::TxDelta::send_stream($fh, @$atd, $pool);
die "Checksum mismatch\nexpected: $exp\ngot: $got\n" if ($got ne $exp);
+ $pool->clear;
close $fh or croak $!;
}
$ENV{'TZ'}="UTC";
our($opt_h,$opt_o,$opt_v,$opt_u,$opt_C,$opt_i,$opt_m,$opt_M,$opt_t,$opt_T,
- $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S);
+ $opt_b,$opt_r,$opt_I,$opt_A,$opt_s,$opt_l,$opt_d,$opt_D,$opt_S,$opt_F);
sub usage() {
print STDERR <<END;
[-o branch-for-HEAD] [-h] [-v] [-l max_rev]
[-C GIT_repository] [-t tagname] [-T trunkname] [-b branchname]
[-d|-D] [-i] [-u] [-r] [-I ignorefilename] [-s start_chg]
- [-m] [-M regex] [-A author_file] [-S] [SVN_URL]
+ [-m] [-M regex] [-A author_file] [-S] [-F] [SVN_URL]
END
exit(1);
}
-getopts("A:b:C:dDhiI:l:mM:o:rs:t:T:Suv") or usage();
+getopts("A:b:C:dDFhiI:l:mM:o:rs:t:T:Suv") or usage();
usage if $opt_h;
my $tag_name = $opt_t || "tags";
}
}
+sub dir_list {
+ my($self,$path,$rev) = @_;
+ my ($dirents,undef,$properties)
+ = $self->{'svn'}->get_dir($path,$rev,undef);
+ return $dirents;
+}
+
package main;
use URI;
open BRANCHES,">>", "$git_dir/svn2git";
-sub node_kind($$$) {
- my ($branch, $path, $revision) = @_;
+sub node_kind($$) {
+ my ($svnpath, $revision) = @_;
my $pool=SVN::Pool->new;
- my $kind = $svn->{'svn'}->check_path(revert_split_path($branch,$path),$revision,$pool);
+ my $kind = $svn->{'svn'}->check_path($svnpath,$revision,$pool);
$pool->clear;
return $kind;
}
-sub revert_split_path($$) {
- my($branch,$path) = @_;
-
- my $svnpath;
- $path = "" if $path eq "/"; # this should not happen, but ...
- if($branch eq "/") {
- $svnpath = "$trunk_name/$path";
- } elsif($branch =~ m#^/#) {
- $svnpath = "$tag_name$branch/$path";
- } else {
- $svnpath = "$branch_name/$branch/$path";
- }
-
- $svnpath =~ s#/+$##;
- return $svnpath;
-}
-
sub get_file($$$) {
- my($rev,$branch,$path) = @_;
-
- my $svnpath = revert_split_path($branch,$path);
+ my($svnpath,$rev,$path) = @_;
# now get it
my ($name,$mode);
}
sub get_ignore($$$$$) {
- my($new,$old,$rev,$branch,$path) = @_;
+ my($new,$old,$rev,$path,$svnpath) = @_;
return unless $opt_I;
- my $svnpath = revert_split_path($branch,$path);
my $name = $svn->ignore("$svnpath",$rev);
if ($path eq '/') {
$path = $opt_I;
close $F;
unlink $name;
push(@$new,['0644',$sha,$path]);
- } else {
+ } elsif (defined $old) {
push(@$old,$path);
}
}
return $therev;
}
+sub expand_svndir($$$);
+
+sub expand_svndir($$$)
+{
+ my ($svnpath, $rev, $path) = @_;
+ my @list;
+ get_ignore(\@list, undef, $rev, $path, $svnpath);
+ my $dirents = $svn->dir_list($svnpath, $rev);
+ foreach my $p(keys %$dirents) {
+ my $kind = node_kind($svnpath.'/'.$p, $rev);
+ if ($kind eq $SVN::Node::file) {
+ my $f = get_file($svnpath.'/'.$p, $rev, $path.'/'.$p);
+ push(@list, $f) if $f;
+ } elsif ($kind eq $SVN::Node::dir) {
+ push(@list,
+ expand_svndir($svnpath.'/'.$p, $rev, $path.'/'.$p));
+ }
+ }
+ return @list;
+}
+
sub copy_path($$$$$$$$) {
# Somebody copied a whole subdirectory.
# We need to find the index entries from the old version which the
my($newrev,$newbranch,$path,$oldpath,$rev,$node_kind,$new,$parents) = @_;
my($srcbranch,$srcpath) = split_path($rev,$oldpath);
- unless(defined $srcbranch) {
- print "Path not found when copying from $oldpath @ $rev\n";
+ unless(defined $srcbranch && defined $srcpath) {
+ print "Path not found when copying from $oldpath @ $rev.\n".
+ "Will try to copy from original SVN location...\n"
+ if $opt_v;
+ push (@$new, expand_svndir($oldpath, $rev, $path));
return;
}
my $therev = branch_rev($srcbranch, $rev);
}
print "$newrev:$newbranch:$path: copying from $srcbranch:$srcpath @ $rev\n" if $opt_v;
if ($node_kind eq $SVN::Node::dir) {
- $srcpath =~ s#/*$#/#;
+ $srcpath =~ s#/*$#/#;
}
my $pid = open my $f,'-|';
$committer_name = $committer_email = $author;
}
- if ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
+ if ($opt_F && $message =~ /From:\s+(.*?)\s+<(.*)>\s*\n/) {
+ ($author_name, $author_email) = ($1, $2);
+ print "Author from From: $1 <$2>\n" if ($opt_v);;
+ } elsif ($opt_S && $message =~ /Signed-off-by:\s+(.*?)\s+<(.*)>\s*\n/) {
($author_name, $author_email) = ($1, $2);
+ print "Author from Signed-off-by: $1 <$2>\n" if ($opt_v);;
} else {
$author_name = $committer_name;
$author_email = $committer_email;
if(defined $oldpath) {
my $p;
($parent,$p) = split_path($revision,$oldpath);
- if($parent eq "/") {
- $parent = $opt_o;
- } else {
- $parent =~ s#^/##; # if it's a tag
+ if(defined $parent) {
+ if($parent eq "/") {
+ $parent = $opt_o;
+ } else {
+ $parent =~ s#^/##; # if it's a tag
+ }
}
} else {
$parent = undef;
push(@old,$path); # remove any old stuff
}
if(($action->[0] eq "A") || ($action->[0] eq "R")) {
- my $node_kind = node_kind($branch,$path,$revision);
+ my $node_kind = node_kind($action->[3], $revision);
if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($revision,$branch,$path);
+ my $f = get_file($action->[3],
+ $revision, $path);
if ($f) {
push(@new,$f) if $f;
} else {
\@new, \@parents);
} else {
get_ignore(\@new, \@old, $revision,
- $branch, $path);
+ $path, $action->[3]);
}
}
} elsif ($action->[0] eq "D") {
push(@old,$path);
} elsif ($action->[0] eq "M") {
- my $node_kind = node_kind($branch,$path,$revision);
+ my $node_kind = node_kind($action->[3], $revision);
if ($node_kind eq $SVN::Node::file) {
- my $f = get_file($revision,$branch,$path);
+ my $f = get_file($action->[3],
+ $revision, $path);
push(@new,$f) if $f;
} elsif ($node_kind eq $SVN::Node::dir) {
get_ignore(\@new, \@old, $revision,
- $branch,$path);
+ $path, $action->[3]);
}
} else {
die "$revision: unknown action '".$action->[0]."' for $path\n";
print $out ("object $cid\n".
"type commit\n".
"tag $dest\n".
- "tagger $committer_name <$committer_email>\n") and
+ "tagger $committer_name <$committer_email> 0 +0000\n") and
close($out)
or die "Cannot create tag object $dest: $!\n";
#include "builtin.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--help] COMMAND [ ARGS ]";
+ "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate] [--bare] [--git-dir=GIT_DIR] [--help] COMMAND [ARGS]";
static void prepend_to_path(const char *dir, int len)
{
{ "check-ref-format", cmd_check_ref_format },
{ "commit-tree", cmd_commit_tree, RUN_SETUP },
{ "count-objects", cmd_count_objects, RUN_SETUP },
- { "diff", cmd_diff, RUN_SETUP },
+ { "diff", cmd_diff, RUN_SETUP | USE_PAGER },
{ "diff-files", cmd_diff_files, RUN_SETUP },
{ "diff-index", cmd_diff_index, RUN_SETUP },
{ "diff-stages", cmd_diff_stages, 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 },
+ { "tar-tree", cmd_tar_tree },
{ "unpack-objects", cmd_unpack_objects, RUN_SETUP },
{ "update-index", cmd_update_index, RUN_SETUP },
{ "update-ref", cmd_update_ref, RUN_SETUP },
Source: http://kernel.org/pub/software/scm/git/%{name}-%{version}.tar.gz
BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel %{!?_without_docs:, xmlto, asciidoc > 6.0.3}
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk
+Requires: git-core, git-svn, git-cvs, git-arch, git-email, gitk, perl-Git
%description
This is a stupid (but extremely fast) directory content manager. It
%description -n gitk
Git revision tree visualiser ('gitk')
+%package -n perl-Git
+Summary: Perl interface to Git
+Group: Development/Libraries
+Requires: git-core = %{version}-%{release}
+Requires: perl(:MODULE_COMPAT_%(eval "`%{__perl} -V:version`"; echo $version))
+BuildRequires: perl(Error)
+
+%description -n perl-Git
+Perl interface to Git
+
%prep
%setup -q
%install
rm -rf $RPM_BUILD_ROOT
make %{_smp_mflags} DESTDIR=$RPM_BUILD_ROOT WITH_OWN_SUBPROCESS_PY=YesPlease \
- prefix=%{_prefix} mandir=%{_mandir} \
+ prefix=%{_prefix} mandir=%{_mandir} INSTALLDIRS=vendor \
install %{!?_without_docs: install-doc}
+find $RPM_BUILD_ROOT -type f -name .packlist -exec rm -f {} ';'
+find $RPM_BUILD_ROOT -type f -name '*.bs' -empty -exec rm -f {} ';'
+find $RPM_BUILD_ROOT -type f -name perllocal.pod -exec rm -f {} ';'
(find $RPM_BUILD_ROOT%{_bindir} -type f | grep -vE "arch|svn|cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@) > bin-man-doc-files
+(find $RPM_BUILD_ROOT%{perl_vendorlib} -type f | sed -e s@^$RPM_BUILD_ROOT@@) >> perl-files
%if %{!?_without_docs:1}0
(find $RPM_BUILD_ROOT%{_mandir} $RPM_BUILD_ROOT/Documentation -type f | grep -vE "arch|svn|git-cvs|email|gitk" | sed -e s@^$RPM_BUILD_ROOT@@ -e 's/$/*/' ) >> bin-man-doc-files
+%else
+rm -rf $RPM_BUILD_ROOT%{_mandir}
%endif
%clean
%{!?_without_docs: %{_mandir}/man1/*gitk*.1*}
%{!?_without_docs: %doc Documentation/*gitk*.html }
+%files -n perl-Git -f perl-files
+%defattr(-,root,root)
+
%files core -f bin-man-doc-files
%defattr(-,root,root)
%{_datadir}/git-core/
GIT web Interface
+=================
The one working on:
http://www.kernel.org/git/
From the git version 1.4.0 gitweb is bundled with git.
-How to configure gitweb for your local system:
+How to configure gitweb for your local system
+---------------------------------------------
You can specify the following configuration variables when building GIT:
* GITWEB_SITENAME
environment variable will be loaded instead of the file
specified when gitweb.cgi was created.
+
+Webserver configuration
+-----------------------
+
+If you want to have one URL for both gitweb and your http://
+repositories, you can configure apache like this:
+
+<VirtualHost www:80>
+ ServerName git.domain.org
+ DocumentRoot /pub/git
+ RewriteEngine on
+ RewriteRule ^/(.*\.git/(?!/?(info|objects|refs)).*)?$ /cgi-bin/gitweb.cgi%{REQUEST_URI} [L,PT]
+ SetEnv GITWEB_CONFIG /etc/gitweb.conf
+</VirtualHost>
+
+The above configuration expects your public repositories to live under
+/pub/git and will serve them as http://git.domain.org/dir-under-pub-git,
+both as cloneable GIT URL and as browseable gitweb interface.
+If you then start your git-daemon with --base-path=/pub/git --export-all
+then you can even use the git:// URL with exactly the same path.
+
+Setting the environment variable GITWEB_CONFIG will tell gitweb to use
+the named file (i.e. in this example /etc/gitweb.conf) as a
+configuration for gitweb. Perl variables defined in here will
+override the defaults given at the head of the gitweb.perl (or
+gitweb.cgi). Look at the comments in that file for information on
+which variables and what they mean.
+
+
Originally written by:
Kay Sievers <kay.sievers@vrfy.org>
color: #880000;
}
+img.logo {
+ float: right;
+ border-width: 0px;
+}
+
div.page_header {
height: 25px;
padding: 8px;
border-collapse: collapse;
}
+table.blame td {
+ padding: 0px 5px;
+ font-size: 12px;
+ vertical-align: top;
+}
+
th {
padding: 2px 5px;
font-size: 12px;
# URI of default stylesheet
our $stylesheet = "++GITWEB_CSS++";
-# URI of GIT logo
+# URI of GIT logo (72x27 size)
our $logo = "++GITWEB_LOGO++";
# URI of GIT favicon, assumed to be image/png type
our $favicon = "++GITWEB_FAVICON++";
+# URI and label (title) of GIT logo link
+#our $logo_url = "http://www.kernel.org/pub/software/scm/git/docs/";
+#our $logo_label = "git documentation";
+our $logo_url = "http://git.or.cz/";
+our $logo_label = "git homepage";
+
# source of projects list
our $projects_list = "++GITWEB_LIST++";
sub gitweb_check_feature {
my ($name) = @_;
- return undef unless exists $feature{$name};
+ return unless exists $feature{$name};
my ($sub, $override, @defaults) = (
$feature{$name}{'sub'},
$feature{$name}{'override'},
return ($ctype, $suffix, $command);
}
+sub gitweb_have_snapshot {
+ my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
+ my $have_snapshot = (defined $ctype && defined $suffix);
+
+ return $have_snapshot;
+}
+
# To enable system wide have in $GITWEB_CONFIG
# $feature{'pickaxe'}{'default'} = [1];
# To have project specific config enable override in $GITWEB_CONFIG
}
}
+# parameters which are pathnames
our $project = $cgi->param('p');
if (defined $project) {
- if (!validate_input($project) ||
+ if (!validate_pathname($project) ||
!(-d "$projectroot/$project") ||
!(-e "$projectroot/$project/HEAD") ||
($export_ok && !(-e "$projectroot/$project/$export_ok")) ||
}
}
-# We have to handle those containing any characters:
our $file_name = $cgi->param('f');
+if (defined $file_name) {
+ if (!validate_pathname($file_name)) {
+ die_error(undef, "Invalid file parameter");
+ }
+}
+
our $file_parent = $cgi->param('fp');
+if (defined $file_parent) {
+ if (!validate_pathname($file_parent)) {
+ die_error(undef, "Invalid file parent parameter");
+ }
+}
+# parameters which are refnames
our $hash = $cgi->param('h');
if (defined $hash) {
- if (!validate_input($hash)) {
+ if (!validate_refname($hash)) {
die_error(undef, "Invalid hash parameter");
}
}
our $hash_parent = $cgi->param('hp');
if (defined $hash_parent) {
- if (!validate_input($hash_parent)) {
+ if (!validate_refname($hash_parent)) {
die_error(undef, "Invalid hash parent parameter");
}
}
our $hash_base = $cgi->param('hb');
if (defined $hash_base) {
- if (!validate_input($hash_base)) {
+ if (!validate_refname($hash_base)) {
die_error(undef, "Invalid hash base parameter");
}
}
our $hash_parent_base = $cgi->param('hpb');
if (defined $hash_parent_base) {
- if (!validate_input($hash_parent_base)) {
+ if (!validate_refname($hash_parent_base)) {
die_error(undef, "Invalid hash parent base parameter");
}
}
+# other parameters
our $page = $cgi->param('pg');
if (defined $page) {
if ($page =~ m/[^0-9]/) {
$project =~ s,/*[^/]*$,,;
}
# validate project
- $project = validate_input($project);
+ $project = validate_pathname($project);
if (!$project ||
($export_ok && !-e "$projectroot/$project/$export_ok") ||
($strict_export && !project_in_list($project))) {
} else {
$action ||= "blob_plain";
}
- $hash_base ||= validate_input($refname);
- $file_name ||= $pathname;
+ $hash_base ||= validate_refname($refname);
+ $file_name ||= validate_pathname($pathname);
} elsif (defined $refname) {
# we got "project.git/branch"
$action ||= "shortlog";
- $hash ||= validate_input($refname);
+ $hash ||= validate_refname($refname);
}
}
evaluate_path_info();
## ======================================================================
## validation, quoting/unquoting and escaping
-sub validate_input {
- my $input = shift;
+sub validate_pathname {
+ my $input = shift || return undef;
- if ($input =~ m/^[0-9a-fA-F]{40}$/) {
- return $input;
+ # no '.' or '..' as elements of path, i.e. no '.' nor '..'
+ # at the beginning, at the end, and between slashes.
+ # also this catches doubled slashes
+ if ($input =~ m!(^|/)(|\.|\.\.)(/|$)!) {
+ return undef;
}
- if ($input =~ m/(^|\/)(|\.|\.\.)($|\/)/) {
+ # no null characters
+ if ($input =~ m!\0!) {
return undef;
}
- if ($input =~ m/[^a-zA-Z0-9_\x80-\xff\ \t\.\/\-\+\#\~\%]/) {
+ return $input;
+}
+
+sub validate_refname {
+ my $input = shift || return undef;
+
+ # textual hashes are O.K.
+ if ($input =~ m/^[0-9a-fA-F]{40}$/) {
+ return $input;
+ }
+ # it must be correct pathname
+ $input = validate_pathname($input)
+ or return undef;
+ # restrictions on ref name according to git-check-ref-format
+ if ($input =~ m!(/\.|\.\.|[\000-\040\177 ~^:?*\[]|/$)!) {
return undef;
}
return $input;
}
+# very thin wrapper for decode("utf8", $str, Encode::FB_DEFAULT);
+sub to_utf8 {
+ my $str = shift;
+ return decode("utf8", $str, Encode::FB_DEFAULT);
+}
+
# quote unsafe chars, but keep the slash, even when it's not
# correct, but quoted slashes look too horrible in bookmarks
sub esc_param {
return $str;
}
+# quote unsafe chars in whole URL, so some charactrs cannot be quoted
+sub esc_url {
+ my $str = shift;
+ $str =~ s/([^A-Za-z0-9\-_.~();\/;?:@&=])/sprintf("%%%02X", ord($1))/eg;
+ $str =~ s/\+/%2B/g;
+ $str =~ s/ /\+/g;
+ return $str;
+}
+
# replace invalid utf8 character with SUBSTITUTION sequence
sub esc_html {
my $str = shift;
- $str = decode("utf8", $str, Encode::FB_DEFAULT);
+ $str = to_utf8($str);
$str = escapeHTML($str);
$str =~ s/\014/^L/g; # escape FORM FEED (FF) character (e.g. in COPYING file)
+ $str =~ s/\033/^[/g; # "escape" ESCAPE (\e) character (e.g. commit 20a3847d8a5032ce41f90dcc68abfb36e6fee9b1)
return $str;
}
if (length($short) < length($long)) {
return $cgi->a({-href => $href, -class => "list subject",
- -title => decode("utf8", $long, Encode::FB_DEFAULT)},
+ -title => to_utf8($long)},
esc_html($short) . $extra);
} else {
return $cgi->a({-href => $href, -class => "list subject"},
my $path = shift || return undef;
my $type = shift;
- my $tree = $base;
+ $path =~ s,/+$,,;
open my $fd, "-|", git_cmd(), "ls-tree", $base, "--", $path
or die_error(undef, "Open git-ls-tree failed");
# '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;
+ open my ($fd), $projects_list or return;
while (my $line = <$fd>) {
chomp $line;
my ($path, $owner) = split ' ', $line;
-e "$projectroot/$path/$export_ok")) {
my $pr = {
path => $path,
- owner => decode("utf8", $owner, Encode::FB_DEFAULT),
+ owner => to_utf8($owner),
};
push @list, $pr
}
$pr = unescape($pr);
$ow = unescape($ow);
if ($pr eq $project) {
- $owner = decode("utf8", $ow, Encode::FB_DEFAULT);
+ $owner = to_utf8($ow);
last;
}
}
last;
}
}
+ if ($co{'title'} eq "") {
+ $co{'title'} = $co{'title_short'} = '(no commit message)';
+ }
# remove added spaces
foreach my $line (@commit_lines) {
$line =~ s/^ //;
}
my $owner = $gcos;
$owner =~ s/[,;].*$//;
- return decode("utf8", $owner, Encode::FB_DEFAULT);
+ return to_utf8($owner);
}
## ......................................................................
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) . " / ";
+ $cgi->a({-href => esc_url($logo_url),
+ -title => $logo_label},
+ qq(<img src="$logo" width="72" height="27" alt="git" class="logo"/>));
+ print $cgi->a({-href => esc_url($home_link)}, $home_link_str) . " / ";
if (defined $project) {
print $cgi->a({-href => href(action=>"summary")}, esc_html($project));
if (defined $action) {
my %base_key = ();
$base_key{hash_base} = $hash_base if defined $hash_base;
+ # The format of a table row is: mode list link. Where mode is
+ # the mode of the entry, list is the name of the entry, an href,
+ # and link is the action links of the entry.
+
print "<td class=\"mode\">" . mode_str($t->{'mode'}) . "</td>\n";
if ($t->{'type'} eq "blob") {
print "<td class=\"list\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
- file_name=>"$basedir$t->{'name'}", %base_key),
- -class => "list"}, esc_html($t->{'name'})) .
- "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
- file_name=>"$basedir$t->{'name'}", %base_key)},
- "blob");
+ $cgi->a({-href => href(action=>"blob", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key),
+ -class => "list"}, esc_html($t->{'name'})) . "</td>\n";
+ print "<td class=\"link\">";
if ($have_blame) {
- print " | " .
- $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
- file_name=>"$basedir$t->{'name'}", %base_key)},
- "blame");
+ print $cgi->a({-href => href(action=>"blame", hash=>$t->{'hash'},
+ file_name=>"$basedir$t->{'name'}", %base_key)},
+ "blame");
}
if (defined $hash_base) {
- print " | " .
- $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ if ($have_blame) {
+ print " | ";
+ }
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
"history");
}
print " | " .
- $cgi->a({-href => href(action=>"blob_plain",
- hash=>$t->{'hash'}, file_name=>"$basedir$t->{'name'}")},
- "raw") .
- "</td>\n";
+ $cgi->a({-href => href(action=>"blob_plain", hash_base=>$hash_base,
+ file_name=>"$basedir$t->{'name'}")},
+ "raw");
+ print "</td>\n";
} elsif ($t->{'type'} eq "tree") {
- print "<td class=\"list\">" .
- $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
- file_name=>"$basedir$t->{'name'}", %base_key)},
- esc_html($t->{'name'})) .
- "</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
+ print "<td class=\"list\">";
+ print $cgi->a({-href => href(action=>"tree", hash=>$t->{'hash'},
file_name=>"$basedir$t->{'name'}", %base_key)},
- "tree");
+ esc_html($t->{'name'}));
+ print "</td>\n";
+ print "<td class=\"link\">";
if (defined $hash_base) {
- print " | " .
- $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash_base,
file_name=>"$basedir$t->{'name'}")},
"history");
}
print "</div>\n";
print "<table class=\"diff_tree\">\n";
- my $alternate = 0;
+ my $alternate = 1;
my $patchno = 0;
foreach my $line (@{$difftree}) {
my %diff = parse_difftree_raw_line($line);
my $mode_chng = "<span class=\"file_status new\">[new $to_file_type";
$mode_chng .= " with mode: $to_mode_str" if $to_mode_str;
$mode_chng .= "]</span>";
- print "<td>" .
- $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ print "<td>";
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
hash_base=>$hash, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'})) .
- "</td>\n" .
- "<td>$mode_chng</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
- hash_base=>$hash, file_name=>$diff{'file'})},
- "blob");
+ -class => "list"}, esc_html($diff{'file'}));
+ print "</td>\n";
+ print "<td>$mode_chng</td>\n";
+ print "<td class=\"link\">";
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print " | " .
- $cgi->a({-href => "#patch$patchno"}, "patch");
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
}
print "</td>\n";
} elsif ($diff{'status'} eq "D") { # deleted
my $mode_chng = "<span class=\"file_status deleted\">[deleted $from_file_type]</span>";
- print "<td>" .
- $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
+ print "<td>";
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
hash_base=>$parent, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'})) .
- "</td>\n" .
- "<td>$mode_chng</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$diff{'from_id'},
- hash_base=>$parent, file_name=>$diff{'file'})},
- "blob") .
- " | ";
+ -class => "list"}, esc_html($diff{'file'}));
+ print "</td>\n";
+ print "<td>$mode_chng</td>\n";
+ print "<td class=\"link\">";
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print " | " .
- $cgi->a({-href => "#patch$patchno"}, "patch");
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
+ print " | ";
}
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
- file_name=>$diff{'file'})},
- "history") .
- "</td>\n";
+ file_name=>$diff{'file'})},
+ "history");
+ print "</td>\n";
} elsif ($diff{'status'} eq "M" || $diff{'status'} eq "T") { # modified, or type changed
my $mode_chnge = "";
$mode_chnge .= "]</span>\n";
}
print "<td>";
- if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
- print $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
- } else { # only mode changed
- print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
- hash_base=>$hash, file_name=>$diff{'file'}),
- -class => "list"}, esc_html($diff{'file'}));
- }
- print "</td>\n" .
- "<td>$mode_chnge</td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
- hash_base=>$hash, file_name=>$diff{'file'})},
- "blob");
+ print $cgi->a({-href => href(action=>"blob", hash=>$diff{'to_id'},
+ hash_base=>$hash, file_name=>$diff{'file'}),
+ -class => "list"}, esc_html($diff{'file'}));
+ print "</td>\n";
+ print "<td>$mode_chnge</td>\n";
+ print "<td class=\"link\">";
if ($diff{'to_id'} ne $diff{'from_id'}) { # modified
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print " | " .
- $cgi->a({-href => "#patch$patchno"}, "patch");
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
} else {
- print " | " .
- $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'file'})},
- "diff");
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'file'})},
+ "diff");
}
+ print " | ";
}
- print " | " .
- $cgi->a({-href => href(action=>"history",
- hash_base=>$hash, file_name=>$diff{'file'})},
- "history");
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$hash,
+ file_name=>$diff{'file'})},
+ "blame") . " | ";
+ print $cgi->a({-href => href(action=>"history", hash_base=>$hash,
+ file_name=>$diff{'file'})},
+ "history");
print "</td>\n";
} elsif ($diff{'status'} eq "R" || $diff{'status'} eq "C") { # renamed or copied
hash=>$diff{'from_id'}, file_name=>$diff{'from_file'}),
-class => "list"}, esc_html($diff{'from_file'})) .
" with " . (int $diff{'similarity'}) . "% similarity$mode_chng]</span></td>\n" .
- "<td class=\"link\">" .
- $cgi->a({-href => href(action=>"blob", hash_base=>$hash,
- hash=>$diff{'to_id'}, file_name=>$diff{'to_file'})},
- "blob");
+ "<td class=\"link\">";
if ($diff{'to_id'} ne $diff{'from_id'}) {
if ($action eq 'commitdiff') {
# link to patch
$patchno++;
- print " | " .
- $cgi->a({-href => "#patch$patchno"}, "patch");
+ print $cgi->a({-href => "#patch$patchno"}, "patch");
} else {
- print " | " .
- $cgi->a({-href => href(action=>"blobdiff",
- hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
- hash_base=>$hash, hash_parent_base=>$parent,
- file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
- "diff");
+ print $cgi->a({-href => href(action=>"blobdiff",
+ hash=>$diff{'to_id'}, hash_parent=>$diff{'from_id'},
+ hash_base=>$hash, hash_parent_base=>$parent,
+ file_name=>$diff{'to_file'}, file_parent=>$diff{'from_file'})},
+ "diff");
}
+ print " | ";
}
+ print $cgi->a({-href => href(action=>"blame", hash_base=>$parent,
+ file_name=>$diff{'from_file'})},
+ "blame") . " | ";
+ print $cgi->a({-href => href(action=>"history", hash_base=>$parent,
+ file_name=>$diff{'from_file'})},
+ "history");
print "</td>\n";
} # we should not encounter Unmerged (U) or Unknown (X) status
print "<div class=\"diff_info\">" . file_type($diffinfo->{'to_mode'}) . ":" .
$cgi->a({-href => href(action=>"blob", hash_base=>$hash,
hash=>$diffinfo->{'to_id'}, file_name=>$diffinfo->{'file'})},
- $diffinfo->{'to_id'}) . "(new)" .
+ $diffinfo->{'to_id'}) . " (new)" .
"</div>\n"; # class="diff_info"
} elsif ($diffinfo->{'status'} eq "D") { # deleted
print "<div class=\"diff_info\">" . file_type($diffinfo->{'from_mode'}) . ":" .
$cgi->a({-href => href(action=>"blob", hash_base=>$hash_parent,
hash=>$diffinfo->{'from_id'}, file_name=>$diffinfo->{'file'})},
- $diffinfo->{'from_id'}) . "(deleted)" .
+ $diffinfo->{'from_id'}) . " (deleted)" .
"</div>\n"; # class="diff_info"
} elsif ($diffinfo->{'status'} eq "R" || # renamed
$to = $#{$revlist} if (!defined $to || $#{$revlist} < $to);
print "<table class=\"shortlog\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $commit = $revlist->[$i];
#my $ref = defined $refs ? format_ref_marker($refs, $commit) : '';
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=>"tree", hash=>$commit, hash_base=>$commit)}, "tree");
+ if (gitweb_have_snapshot()) {
+ print " | " . $cgi->a({-href => href(action=>"snapshot", hash=>$commit)}, "snapshot");
+ }
print "</td>\n" .
"</tr>\n";
}
$to = $#{$revlist} unless (defined $to && $to <= $#{$revlist});
print "<table class=\"history\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
if ($revlist->[$i] !~ m/^([0-9a-fA-F]{40})/) {
next;
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);
+ $cgi->a({-href => href(action=>$ftype, hash_base=>$commit, file_name=>$file_name)}, $ftype) . " | " .
+ $cgi->a({-href => href(action=>"commitdiff", hash=>$commit)}, "commitdiff");
if ($ftype eq 'blob') {
my $blob_current = git_get_hash_by_path($hash_base, $file_name);
$to = $#{$taglist} if (!defined $to || $#{$taglist} < $to);
print "<table class=\"tags\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $entry = $taglist->[$i];
my %tag = %$entry;
$to = $#{$headlist} if (!defined $to || $#{$headlist} < $to);
print "<table class=\"heads\" cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
for (my $i = $from; $i <= $to; $i++) {
my $entry = $headlist->[$i];
my %tag = %$entry;
}
print "<th></th>\n" .
"</tr>\n";
- my $alternate = 0;
+ my $alternate = 1;
foreach my $pr (@projects) {
if ($alternate) {
print "<tr class=\"dark\">\n";
print $cgi->header(
-type => 'text/plain',
-charset => 'utf-8',
- -content_disposition => qq(inline; filename="index.aux"));
+ -content_disposition => 'inline; filename="index.aux"');
foreach my $pr (@projects) {
if (!exists $pr->{'owner'}) {
print $cgi->header(
-type => "$type",
-expires=>$expires,
- -content_disposition => "inline; filename=\"$save_as\"");
+ -content_disposition => 'inline; filename="' . "$save_as" . '"');
undef $/;
binmode STDOUT, ':raw';
print <$fd>;
}
sub git_tree {
- my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
- my $have_snapshot = (defined $ctype && defined $suffix);
+ my $have_snapshot = gitweb_have_snapshot();
+ if (!defined $hash_base) {
+ $hash_base = "HEAD";
+ }
if (!defined $hash) {
- $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");
- }
- if (!defined $hash_base) {
- $hash_base = $hash;
+ $hash = git_get_hash_by_path($hash_base, $file_name, "tree");
+ } else {
+ $hash = $hash_base;
}
}
$/ = "\0";
git_print_page_path($file_name, 'tree', $hash_base);
print "<div class=\"page_body\">\n";
print "<table cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
foreach my $line (@entries) {
my %t = parse_ls_tree_line($line, -z => 1);
}
sub git_snapshot {
-
my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
my $have_snapshot = (defined $ctype && defined $suffix);
if (!$have_snapshot) {
my $filename = basename($project) . "-$hash.tar.$suffix";
- print $cgi->header(-type => 'application/x-tar',
- -content_encoding => $ctype,
- -content_disposition => "inline; filename=\"$filename\"",
- -status => '200 OK');
-
- my $git_command = git_cmd_str();
- open my $fd, "-|", "$git_command tar-tree $hash \'$project\' | $command" or
- die_error(undef, "Execute git-tar-tree failed.");
+ print $cgi->header(
+ -type => 'application/x-tar',
+ -content_encoding => $ctype,
+ -content_disposition => 'inline; filename="' . "$filename" . '"',
+ -status => '200 OK');
+
+ my $git = git_cmd_str();
+ my $name = $project;
+ $name =~ s/\047/\047\\\047\047/g;
+ open my $fd, "-|",
+ "$git archive --format=tar --prefix=\'$name\'/ $hash | $command"
+ or die_error(undef, "Execute git-tar-tree failed.");
binmode STDOUT, ':raw';
print <$fd>;
binmode STDOUT, ':utf8'; # as set at the beginning of gitweb.cgi
my $refs = git_get_references();
my $ref = format_ref_marker($refs, $co{'id'});
- my ($ctype, $suffix, $command) = gitweb_check_feature('snapshot');
- my $have_snapshot = (defined $ctype && defined $suffix);
+ my $have_snapshot = gitweb_have_snapshot();
my @views_nav = ();
if (defined $file_name && defined $co{'parent'}) {
- my $parent = $co{'parent'};
push @views_nav,
$cgi->a({-href => href(action=>"blame", hash_parent=>$parent, file_name=>$file_name)},
"blame");
}
- if (defined $co{'parent'}) {
- push @views_nav,
- $cgi->a({-href => href(action=>"shortlog", hash=>$hash)}, "shortlog"),
- $cgi->a({-href => href(action=>"log", hash=>$hash)}, "log");
- }
git_header_html(undef, $expires);
- git_print_page_nav('commit', defined $co{'parent'} ? '' : 'commitdiff',
+ git_print_page_nav('commit', '',
$hash, $co{'tree'}, $hash,
join (' | ', @views_nav));
-type => 'text/plain',
-charset => 'utf-8',
-expires => $expires,
- -content_disposition => qq(inline; filename=") . quotemeta($file_name) . qq(.patch"));
+ -content_disposition => 'inline; filename="' . "$file_name" . '.patch"');
print "X-Git-Url: " . $cgi->self_url() . "\n\n";
-type => 'text/plain',
-charset => 'utf-8',
-expires => $expires,
- -content_disposition => qq(inline; filename="$filename"));
+ -content_disposition => 'inline; filename="' . "$filename" . '"');
my %ad = parse_date($co{'author_epoch'}, $co{'author_tz'});
print <<TEXT;
From: $co{'author'}
git_print_header_div('commit', esc_html($co{'title'}), $hash);
print "<table cellspacing=\"0\">\n";
- my $alternate = 0;
+ my $alternate = 1;
if ($commit_search) {
$/ = "\0";
open my $fd, "-|", git_cmd(), "rev-list", "--header", "--parents", $hash or next;
"<![CDATA[\n";
my $comment = $co{'comment'};
foreach my $line (@$comment) {
- $line = decode("utf8", $line, Encode::FB_DEFAULT);
+ $line = to_utf8($line);
print "$line<br/>\n";
}
print "<br/>\n";
next;
}
my $file = esc_html(unquote($7));
- $file = decode("utf8", $file, Encode::FB_DEFAULT);
+ $file = to_utf8($file);
print "$file<br/>\n";
}
print "]]>\n" .
#include "fetch.h"
#include "http.h"
-#ifndef NO_EXPAT
-#include <expat.h>
-
-/* Definitions for DAV requests */
-#define DAV_PROPFIND "PROPFIND"
-#define DAV_PROPFIND_RESP ".multistatus.response"
-#define DAV_PROPFIND_NAME ".multistatus.response.href"
-#define DAV_PROPFIND_COLLECTION ".multistatus.response.propstat.prop.resourcetype.collection"
-#define PROPFIND_ALL_REQUEST "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<D:propfind xmlns:D=\"DAV:\">\n<D:allprop/>\n</D:propfind>"
-
-/* Definitions for processing XML DAV responses */
-#ifndef XML_STATUS_OK
-enum XML_Status {
- XML_STATUS_OK = 1,
- XML_STATUS_ERROR = 0
-};
-#define XML_STATUS_OK 1
-#define XML_STATUS_ERROR 0
-#endif
-
-/* Flags that control remote_ls processing */
-#define PROCESS_FILES (1u << 0)
-#define PROCESS_DIRS (1u << 1)
-#define RECURSIVE (1u << 2)
-
-/* Flags that remote_ls passes to callback functions */
-#define IS_DIR (1u << 0)
-#endif
-
#define PREV_BUF_SIZE 4096
#define RANGE_HEADER_SIZE 30
int http_specific;
};
-#ifndef NO_EXPAT
-struct xml_ctx
-{
- char *name;
- int len;
- char *cdata;
- void (*userFunc)(struct xml_ctx *ctx, int tag_closed);
- void *userData;
-};
-
-struct remote_ls_ctx
-{
- struct alt_base *repo;
- char *path;
- void (*userFunc)(struct remote_ls_ctx *ls);
- void *userData;
- int flags;
- char *dentry_name;
- int dentry_flags;
- int rc;
- struct remote_ls_ctx *parent;
-};
-#endif
-
static struct object_request *object_queue_head;
static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb,
free(url);
}
-#ifndef NO_EXPAT
-static void
-xml_start_tag(void *userData, const char *name, const char **atts)
-{
- struct xml_ctx *ctx = (struct xml_ctx *)userData;
- const char *c = strchr(name, ':');
- int new_len;
-
- if (c == NULL)
- c = name;
- else
- c++;
-
- new_len = strlen(ctx->name) + strlen(c) + 2;
-
- if (new_len > ctx->len) {
- ctx->name = xrealloc(ctx->name, new_len);
- ctx->len = new_len;
- }
- strcat(ctx->name, ".");
- strcat(ctx->name, c);
-
- free(ctx->cdata);
- ctx->cdata = NULL;
-
- ctx->userFunc(ctx, 0);
-}
-
-static void
-xml_end_tag(void *userData, const char *name)
-{
- struct xml_ctx *ctx = (struct xml_ctx *)userData;
- const char *c = strchr(name, ':');
- char *ep;
-
- ctx->userFunc(ctx, 1);
-
- if (c == NULL)
- c = name;
- else
- c++;
-
- ep = ctx->name + strlen(ctx->name) - strlen(c) - 1;
- *ep = 0;
-}
-
-static void
-xml_cdata(void *userData, const XML_Char *s, int len)
-{
- struct xml_ctx *ctx = (struct xml_ctx *)userData;
- free(ctx->cdata);
- ctx->cdata = xmalloc(len + 1);
- strlcpy(ctx->cdata, s, len + 1);
-}
-
-static int remote_ls(struct alt_base *repo, const char *path, int flags,
- void (*userFunc)(struct remote_ls_ctx *ls),
- void *userData);
-
-static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed)
-{
- struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData;
-
- if (tag_closed) {
- if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) {
- if (ls->dentry_flags & IS_DIR) {
- if (ls->flags & PROCESS_DIRS) {
- ls->userFunc(ls);
- }
- if (strcmp(ls->dentry_name, ls->path) &&
- ls->flags & RECURSIVE) {
- ls->rc = remote_ls(ls->repo,
- ls->dentry_name,
- ls->flags,
- ls->userFunc,
- ls->userData);
- }
- } else if (ls->flags & PROCESS_FILES) {
- ls->userFunc(ls);
- }
- } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) {
- ls->dentry_name = xmalloc(strlen(ctx->cdata) -
- ls->repo->path_len + 1);
- strcpy(ls->dentry_name, ctx->cdata + ls->repo->path_len);
- } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) {
- ls->dentry_flags |= IS_DIR;
- }
- } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) {
- free(ls->dentry_name);
- ls->dentry_name = NULL;
- ls->dentry_flags = 0;
- }
-}
-
-static int remote_ls(struct alt_base *repo, const char *path, int flags,
- void (*userFunc)(struct remote_ls_ctx *ls),
- void *userData)
-{
- char *url = xmalloc(strlen(repo->base) + strlen(path) + 1);
- struct active_request_slot *slot;
- struct slot_results results;
- struct buffer in_buffer;
- struct buffer out_buffer;
- char *in_data;
- char *out_data;
- XML_Parser parser = XML_ParserCreate(NULL);
- enum XML_Status result;
- struct curl_slist *dav_headers = NULL;
- struct xml_ctx ctx;
- struct remote_ls_ctx ls;
-
- ls.flags = flags;
- ls.repo = repo;
- ls.path = xstrdup(path);
- ls.dentry_name = NULL;
- ls.dentry_flags = 0;
- ls.userData = userData;
- ls.userFunc = userFunc;
- ls.rc = 0;
-
- sprintf(url, "%s%s", repo->base, path);
-
- out_buffer.size = strlen(PROPFIND_ALL_REQUEST);
- out_data = xmalloc(out_buffer.size + 1);
- snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST);
- out_buffer.posn = 0;
- out_buffer.buffer = out_data;
-
- in_buffer.size = 4096;
- in_data = xmalloc(in_buffer.size);
- in_buffer.posn = 0;
- in_buffer.buffer = in_data;
-
- dav_headers = curl_slist_append(dav_headers, "Depth: 1");
- dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
-
- slot = get_active_slot();
- slot->results = &results;
- curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size);
- curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
- curl_easy_setopt(slot->curl, CURLOPT_URL, url);
- curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
- curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND);
- curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
-
- if (start_active_slot(slot)) {
- run_active_slot(slot);
- if (results.curl_result == CURLE_OK) {
- ctx.name = xcalloc(10, 1);
- ctx.len = 0;
- ctx.cdata = NULL;
- ctx.userFunc = handle_remote_ls_ctx;
- ctx.userData = &ls;
- XML_SetUserData(parser, &ctx);
- XML_SetElementHandler(parser, xml_start_tag,
- xml_end_tag);
- XML_SetCharacterDataHandler(parser, xml_cdata);
- result = XML_Parse(parser, in_buffer.buffer,
- in_buffer.posn, 1);
- free(ctx.name);
-
- if (result != XML_STATUS_OK) {
- ls.rc = error("XML error: %s",
- XML_ErrorString(
- XML_GetErrorCode(parser)));
- }
- } else {
- ls.rc = -1;
- }
- } else {
- ls.rc = error("Unable to start PROPFIND request");
- }
-
- free(ls.path);
- free(url);
- free(out_data);
- free(in_buffer.buffer);
- curl_slist_free_all(dav_headers);
-
- return ls.rc;
-}
-
-static void process_ls_pack(struct remote_ls_ctx *ls)
-{
- unsigned char sha1[20];
-
- if (strlen(ls->dentry_name) == 63 &&
- !strncmp(ls->dentry_name, "objects/pack/pack-", 18) &&
- has_extension(ls->dentry_name, ".pack")) {
- get_sha1_hex(ls->dentry_name + 18, sha1);
- setup_index(ls->repo, sha1);
- }
-}
-#endif
-
static int fetch_indices(struct alt_base *repo)
{
unsigned char sha1[20];
if (get_verbosely)
fprintf(stderr, "Getting pack list for %s\n", repo->base);
-#ifndef NO_EXPAT
- if (remote_ls(repo, "objects/pack/", PROCESS_FILES,
- process_ls_pack, NULL) == 0)
- return 0;
-#endif
-
url = xmalloc(strlen(repo->base) + 21);
sprintf(url, "%s/objects/info/packs", repo->base);
char *ssl_cainfo = NULL;
long curl_low_speed_limit = -1;
long curl_low_speed_time = -1;
+int curl_ftp_no_epsv = 0;
struct curl_slist *pragma_header;
return 0;
}
+ if (!strcmp("http.noepsv", var)) {
+ curl_ftp_no_epsv = git_config_bool(var, value);
+ return 0;
+ }
+
/* Fall back on the default ones */
return git_default_config(var, value);
}
curl_easy_setopt(result, CURLOPT_USERAGENT, GIT_USER_AGENT);
+ if (curl_ftp_no_epsv)
+ curl_easy_setopt(result, CURLOPT_FTP_USE_EPSV, 0);
+
return result;
}
max_requests = DEFAULT_MAX_REQUESTS;
#endif
+ if (getenv("GIT_CURL_FTP_NO_EPSV"))
+ curl_ftp_no_epsv = 1;
+
#ifndef NO_CURL_EASY_DUPHANDLE
curl_default = get_curl_handle();
#endif
if (msg->len < 5 || strncmp( data, "From ", 5 ))
return 0;
+ p = strchr( data, '\n' );
+ if (p) {
+ p = &p[1];
+ msg->len -= p-data;
+ *ofs += p-data;
+ data = p;
+ }
+
p = strstr( data, "\nFrom " );
if (p)
msg->len = &p[1] - data;
#include "interpolate.h"
-void interp_set_entry(struct interp *table, int slot, char *value)
+void interp_set_entry(struct interp *table, int slot, const char *value)
{
char *oldval = table[slot].value;
- char *newval = value;
+ char *newval = NULL;
if (oldval)
free(oldval);
char *value;
};
-extern void interp_set_entry(struct interp *table, int slot, char *value);
+extern void interp_set_entry(struct interp *table, int slot, const char *value);
extern void interp_clear_table(struct interp *table, int ninterps);
extern int interpolate(char *result, int reslen,
if (merged_common_ancestors == NULL) {
/* if there is no common ancestor, make an empty tree */
struct tree *tree = xcalloc(1, sizeof(struct tree));
- unsigned char hdr[40];
- int hdrlen;
tree->object.parsed = 1;
tree->object.type = OBJ_TREE;
- write_sha1_file_prepare(NULL, 0, tree_type, tree->object.sha1,
- hdr, &hdrlen);
+ hash_sha1_file(NULL, 0, tree_type, tree->object.sha1);
merged_common_ancestors = make_virtual_commit(tree, "ancestor");
}
* Packed object header
*/
#define PACK_SIGNATURE 0x5041434b /* "PACK" */
-#define PACK_VERSION 3
+#define PACK_VERSION 2
#define pack_version_ok(v) ((v) == htonl(2) || (v) == htonl(3))
struct pack_header {
unsigned int hdr_signature;
--- /dev/null
+Makefile
+blib
+blibdirs
+pm_to_blib
--- /dev/null
+=head1 NAME
+
+Git - Perl interface to the Git version control system
+
+=cut
+
+
+package Git;
+
+use strict;
+
+
+BEGIN {
+
+our ($VERSION, @ISA, @EXPORT, @EXPORT_OK);
+
+# Totally unstable API.
+$VERSION = '0.01';
+
+
+=head1 SYNOPSIS
+
+ use Git;
+
+ my $version = Git::command_oneline('version');
+
+ git_cmd_try { Git::command_noisy('update-server-info') }
+ '%s failed w/ code %d';
+
+ my $repo = Git->repository (Directory => '/srv/git/cogito.git');
+
+
+ my @revs = $repo->command('rev-list', '--since=last monday', '--all');
+
+ my ($fh, $c) = $repo->command_output_pipe('rev-list', '--since=last monday', '--all');
+ my $lastrev = <$fh>; chomp $lastrev;
+ $repo->command_close_pipe($fh, $c);
+
+ my $lastrev = $repo->command_oneline( [ 'rev-list', '--all' ],
+ STDERR => 0 );
+
+=cut
+
+
+require Exporter;
+
+@ISA = qw(Exporter);
+
+@EXPORT = qw(git_cmd_try);
+
+# Methods which can be called as standalone functions as well:
+@EXPORT_OK = qw(command command_oneline command_noisy
+ command_output_pipe command_input_pipe command_close_pipe
+ version exec_path hash_object git_cmd_try);
+
+
+=head1 DESCRIPTION
+
+This module provides Perl scripts easy way to interface the Git version control
+system. The modules have an easy and well-tested way to call arbitrary Git
+commands; in the future, the interface will also provide specialized methods
+for doing easily operations which are not totally trivial to do over
+the generic command interface.
+
+While some commands can be executed outside of any context (e.g. 'version'
+or 'init-db'), most operations require a repository context, which in practice
+means getting an instance of the Git object using the repository() constructor.
+(In the future, we will also get a new_repository() constructor.) All commands
+called as methods of the object are then executed in the context of the
+repository.
+
+Part of the "repository state" is also information about path to the attached
+working copy (unless you work with a bare repository). You can also navigate
+inside of the working copy using the C<wc_chdir()> method. (Note that
+the repository object is self-contained and will not change working directory
+of your process.)
+
+TODO: In the future, we might also do
+
+ my $remoterepo = $repo->remote_repository (Name => 'cogito', Branch => 'master');
+ $remoterepo ||= Git->remote_repository ('http://git.or.cz/cogito.git/');
+ my @refs = $remoterepo->refs();
+
+Currently, the module merely wraps calls to external Git tools. In the future,
+it will provide a much faster way to interact with Git by linking directly
+to libgit. This should be completely opaque to the user, though (performance
+increate nonwithstanding).
+
+=cut
+
+
+use Carp qw(carp croak); # but croak is bad - throw instead
+use Error qw(:try);
+use Cwd qw(abs_path);
+
+}
+
+
+=head1 CONSTRUCTORS
+
+=over 4
+
+=item repository ( OPTIONS )
+
+=item repository ( DIRECTORY )
+
+=item repository ()
+
+Construct a new repository object.
+C<OPTIONS> are passed in a hash like fashion, using key and value pairs.
+Possible options are:
+
+B<Repository> - Path to the Git repository.
+
+B<WorkingCopy> - Path to the associated working copy; not strictly required
+as many commands will happily crunch on a bare repository.
+
+B<WorkingSubdir> - Subdirectory in the working copy to work inside.
+Just left undefined if you do not want to limit the scope of operations.
+
+B<Directory> - Path to the Git working directory in its usual setup.
+The C<.git> directory is searched in the directory and all the parent
+directories; if found, C<WorkingCopy> is set to the directory containing
+it and C<Repository> to the C<.git> directory itself. If no C<.git>
+directory was found, the C<Directory> is assumed to be a bare repository,
+C<Repository> is set to point at it and C<WorkingCopy> is left undefined.
+If the C<$GIT_DIR> environment variable is set, things behave as expected
+as well.
+
+You should not use both C<Directory> and either of C<Repository> and
+C<WorkingCopy> - the results of that are undefined.
+
+Alternatively, a directory path may be passed as a single scalar argument
+to the constructor; it is equivalent to setting only the C<Directory> option
+field.
+
+Calling the constructor with no options whatsoever is equivalent to
+calling it with C<< Directory => '.' >>. In general, if you are building
+a standard porcelain command, simply doing C<< Git->repository() >> should
+do the right thing and setup the object to reflect exactly where the user
+is right now.
+
+=cut
+
+sub repository {
+ my $class = shift;
+ my @args = @_;
+ my %opts = ();
+ my $self;
+
+ if (defined $args[0]) {
+ if ($#args % 2 != 1) {
+ # Not a hash.
+ $#args == 0 or throw Error::Simple("bad usage");
+ %opts = ( Directory => $args[0] );
+ } else {
+ %opts = @args;
+ }
+ }
+
+ if (not defined $opts{Repository} and not defined $opts{WorkingCopy}) {
+ $opts{Directory} ||= '.';
+ }
+
+ if ($opts{Directory}) {
+ -d $opts{Directory} or throw Error::Simple("Directory not found: $!");
+
+ my $search = Git->repository(WorkingCopy => $opts{Directory});
+ my $dir;
+ try {
+ $dir = $search->command_oneline(['rev-parse', '--git-dir'],
+ STDERR => 0);
+ } catch Git::Error::Command with {
+ $dir = undef;
+ };
+
+ if ($dir) {
+ $dir =~ m#^/# or $dir = $opts{Directory} . '/' . $dir;
+ $opts{Repository} = $dir;
+
+ # If --git-dir went ok, this shouldn't die either.
+ my $prefix = $search->command_oneline('rev-parse', '--show-prefix');
+ $dir = abs_path($opts{Directory}) . '/';
+ if ($prefix) {
+ if (substr($dir, -length($prefix)) ne $prefix) {
+ throw Error::Simple("rev-parse confused me - $dir does not have trailing $prefix");
+ }
+ substr($dir, -length($prefix)) = '';
+ }
+ $opts{WorkingCopy} = $dir;
+ $opts{WorkingSubdir} = $prefix;
+
+ } else {
+ # A bare repository? Let's see...
+ $dir = $opts{Directory};
+
+ unless (-d "$dir/refs" and -d "$dir/objects" and -e "$dir/HEAD") {
+ # Mimick git-rev-parse --git-dir error message:
+ throw Error::Simple('fatal: Not a git repository');
+ }
+ my $search = Git->repository(Repository => $dir);
+ try {
+ $search->command('symbolic-ref', 'HEAD');
+ } catch Git::Error::Command with {
+ # Mimick git-rev-parse --git-dir error message:
+ throw Error::Simple('fatal: Not a git repository');
+ }
+
+ $opts{Repository} = abs_path($dir);
+ }
+
+ delete $opts{Directory};
+ }
+
+ $self = { opts => \%opts };
+ bless $self, $class;
+}
+
+
+=back
+
+=head1 METHODS
+
+=over 4
+
+=item command ( COMMAND [, ARGUMENTS... ] )
+
+=item command ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given Git C<COMMAND> (specify it without the 'git-'
+prefix), optionally with the specified extra C<ARGUMENTS>.
+
+The second more elaborate form can be used if you want to further adjust
+the command execution. Currently, only one option is supported:
+
+B<STDERR> - How to deal with the command's error output. By default (C<undef>)
+it is delivered to the caller's C<STDERR>. A false value (0 or '') will cause
+it to be thrown away. If you want to process it, you can get it in a filehandle
+you specify, but you must be extremely careful; if the error output is not
+very short and you want to read it in the same process as where you called
+C<command()>, you are set up for a nice deadlock!
+
+The method can be called without any instance or on a specified Git repository
+(in that case the command will be run in the repository context).
+
+In scalar context, it returns all the command output in a single string
+(verbatim).
+
+In array context, it returns an array containing lines printed to the
+command's stdout (without trailing newlines).
+
+In both cases, the command's stdin and stderr are the same as the caller's.
+
+=cut
+
+sub command {
+ my ($fh, $ctx) = command_output_pipe(@_);
+
+ if (not defined wantarray) {
+ # Nothing to pepper the possible exception with.
+ _cmd_close($fh, $ctx);
+
+ } elsif (not wantarray) {
+ local $/;
+ my $text = <$fh>;
+ try {
+ _cmd_close($fh, $ctx);
+ } catch Git::Error::Command with {
+ # Pepper with the output:
+ my $E = shift;
+ $E->{'-outputref'} = \$text;
+ throw $E;
+ };
+ return $text;
+
+ } else {
+ my @lines = <$fh>;
+ chomp @lines;
+ try {
+ _cmd_close($fh, $ctx);
+ } catch Git::Error::Command with {
+ my $E = shift;
+ $E->{'-outputref'} = \@lines;
+ throw $E;
+ };
+ return @lines;
+ }
+}
+
+
+=item command_oneline ( COMMAND [, ARGUMENTS... ] )
+
+=item command_oneline ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command()
+does but always return a scalar string containing the first line
+of the command's standard output.
+
+=cut
+
+sub command_oneline {
+ my ($fh, $ctx) = command_output_pipe(@_);
+
+ my $line = <$fh>;
+ defined $line and chomp $line;
+ try {
+ _cmd_close($fh, $ctx);
+ } catch Git::Error::Command with {
+ # Pepper with the output:
+ my $E = shift;
+ $E->{'-outputref'} = \$line;
+ throw $E;
+ };
+ return $line;
+}
+
+
+=item command_output_pipe ( COMMAND [, ARGUMENTS... ] )
+
+=item command_output_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command()
+does but return a pipe filehandle from which the command output can be
+read.
+
+The function can return C<($pipe, $ctx)> in array context.
+See C<command_close_pipe()> for details.
+
+=cut
+
+sub command_output_pipe {
+ _command_common_pipe('-|', @_);
+}
+
+
+=item command_input_pipe ( COMMAND [, ARGUMENTS... ] )
+
+=item command_input_pipe ( [ COMMAND, ARGUMENTS... ], { Opt => Val ... } )
+
+Execute the given C<COMMAND> in the same way as command_output_pipe()
+does but return an input pipe filehandle instead; the command output
+is not captured.
+
+The function can return C<($pipe, $ctx)> in array context.
+See C<command_close_pipe()> for details.
+
+=cut
+
+sub command_input_pipe {
+ _command_common_pipe('|-', @_);
+}
+
+
+=item command_close_pipe ( PIPE [, CTX ] )
+
+Close the C<PIPE> as returned from C<command_*_pipe()>, checking
+whether the command finished successfuly. The optional C<CTX> argument
+is required if you want to see the command name in the error message,
+and it is the second value returned by C<command_*_pipe()> when
+called in array context. The call idiom is:
+
+ my ($fh, $ctx) = $r->command_output_pipe('status');
+ while (<$fh>) { ... }
+ $r->command_close_pipe($fh, $ctx);
+
+Note that you should not rely on whatever actually is in C<CTX>;
+currently it is simply the command name but in future the context might
+have more complicated structure.
+
+=cut
+
+sub command_close_pipe {
+ my ($self, $fh, $ctx) = _maybe_self(@_);
+ $ctx ||= '<unknown>';
+ _cmd_close($fh, $ctx);
+}
+
+
+=item command_noisy ( COMMAND [, ARGUMENTS... ] )
+
+Execute the given C<COMMAND> in the same way as command() does but do not
+capture the command output - the standard output is not redirected and goes
+to the standard output of the caller application.
+
+While the method is called command_noisy(), you might want to as well use
+it for the most silent Git commands which you know will never pollute your
+stdout but you want to avoid the overhead of the pipe setup when calling them.
+
+The function returns only after the command has finished running.
+
+=cut
+
+sub command_noisy {
+ my ($self, $cmd, @args) = _maybe_self(@_);
+ _check_valid_cmd($cmd);
+
+ my $pid = fork;
+ if (not defined $pid) {
+ throw Error::Simple("fork failed: $!");
+ } elsif ($pid == 0) {
+ _cmd_exec($self, $cmd, @args);
+ }
+ if (waitpid($pid, 0) > 0 and $?>>8 != 0) {
+ throw Git::Error::Command(join(' ', $cmd, @args), $? >> 8);
+ }
+}
+
+
+=item version ()
+
+Return the Git version in use.
+
+=cut
+
+sub version {
+ my $verstr = command_oneline('--version');
+ $verstr =~ s/^git version //;
+ $verstr;
+}
+
+
+=item exec_path ()
+
+Return path to the Git sub-command executables (the same as
+C<git --exec-path>). Useful mostly only internally.
+
+=cut
+
+sub exec_path { command_oneline('--exec-path') }
+
+
+=item repo_path ()
+
+Return path to the git repository. Must be called on a repository instance.
+
+=cut
+
+sub repo_path { $_[0]->{opts}->{Repository} }
+
+
+=item wc_path ()
+
+Return path to the working copy. Must be called on a repository instance.
+
+=cut
+
+sub wc_path { $_[0]->{opts}->{WorkingCopy} }
+
+
+=item wc_subdir ()
+
+Return path to the subdirectory inside of a working copy. Must be called
+on a repository instance.
+
+=cut
+
+sub wc_subdir { $_[0]->{opts}->{WorkingSubdir} ||= '' }
+
+
+=item wc_chdir ( SUBDIR )
+
+Change the working copy subdirectory to work within. The C<SUBDIR> is
+relative to the working copy root directory (not the current subdirectory).
+Must be called on a repository instance attached to a working copy
+and the directory must exist.
+
+=cut
+
+sub wc_chdir {
+ my ($self, $subdir) = @_;
+ $self->wc_path()
+ or throw Error::Simple("bare repository");
+
+ -d $self->wc_path().'/'.$subdir
+ or throw Error::Simple("subdir not found: $!");
+ # Of course we will not "hold" the subdirectory so anyone
+ # can delete it now and we will never know. But at least we tried.
+
+ $self->{opts}->{WorkingSubdir} = $subdir;
+}
+
+
+=item config ( VARIABLE )
+
+Retrieve the configuration C<VARIABLE> in the same manner as C<repo-config>
+does. In scalar context requires the variable to be set only one time
+(exception is thrown otherwise), in array context returns allows the
+variable to be set multiple times and returns all the values.
+
+Must be called on a repository instance.
+
+This currently wraps command('repo-config') so it is not so fast.
+
+=cut
+
+sub config {
+ my ($self, $var) = @_;
+ $self->repo_path()
+ or throw Error::Simple("not a repository");
+
+ try {
+ if (wantarray) {
+ return $self->command('repo-config', '--get-all', $var);
+ } else {
+ return $self->command_oneline('repo-config', '--get', $var);
+ }
+ } catch Git::Error::Command with {
+ my $E = shift;
+ if ($E->value() == 1) {
+ # Key not found.
+ return undef;
+ } else {
+ throw $E;
+ }
+ };
+}
+
+
+=item ident ( TYPE | IDENTSTR )
+
+=item ident_person ( TYPE | IDENTSTR | IDENTARRAY )
+
+This suite of functions retrieves and parses ident information, as stored
+in the commit and tag objects or produced by C<var GIT_type_IDENT> (thus
+C<TYPE> can be either I<author> or I<committer>; case is insignificant).
+
+The C<ident> method retrieves the ident information from C<git-var>
+and either returns it as a scalar string or as an array with the fields parsed.
+Alternatively, it can take a prepared ident string (e.g. from the commit
+object) and just parse it.
+
+C<ident_person> returns the person part of the ident - name and email;
+it can take the same arguments as C<ident> or the array returned by C<ident>.
+
+The synopsis is like:
+
+ my ($name, $email, $time_tz) = ident('author');
+ "$name <$email>" eq ident_person('author');
+ "$name <$email>" eq ident_person($name);
+ $time_tz =~ /^\d+ [+-]\d{4}$/;
+
+Both methods must be called on a repository instance.
+
+=cut
+
+sub ident {
+ my ($self, $type) = @_;
+ my $identstr;
+ if (lc $type eq lc 'committer' or lc $type eq lc 'author') {
+ $identstr = $self->command_oneline('var', 'GIT_'.uc($type).'_IDENT');
+ } else {
+ $identstr = $type;
+ }
+ if (wantarray) {
+ return $identstr =~ /^(.*) <(.*)> (\d+ [+-]\d{4})$/;
+ } else {
+ return $identstr;
+ }
+}
+
+sub ident_person {
+ my ($self, @ident) = @_;
+ $#ident == 0 and @ident = $self->ident($ident[0]);
+ return "$ident[0] <$ident[1]>";
+}
+
+
+=item hash_object ( TYPE, FILENAME )
+
+Compute the SHA1 object id of the given C<FILENAME> (or data waiting in
+C<FILEHANDLE>) considering it is of the C<TYPE> object type (C<blob>,
+C<commit>, C<tree>).
+
+The method can be called without any instance or on a specified Git repository,
+it makes zero difference.
+
+The function returns the SHA1 hash.
+
+=cut
+
+# TODO: Support for passing FILEHANDLE instead of FILENAME
+sub hash_object {
+ my ($self, $type, $file) = _maybe_self(@_);
+ command_oneline('hash-object', '-t', $type, $file);
+}
+
+
+
+=back
+
+=head1 ERROR HANDLING
+
+All functions are supposed to throw Perl exceptions in case of errors.
+See the L<Error> module on how to catch those. Most exceptions are mere
+L<Error::Simple> instances.
+
+However, the C<command()>, C<command_oneline()> and C<command_noisy()>
+functions suite can throw C<Git::Error::Command> exceptions as well: those are
+thrown when the external command returns an error code and contain the error
+code as well as access to the captured command's output. The exception class
+provides the usual C<stringify> and C<value> (command's exit code) methods and
+in addition also a C<cmd_output> method that returns either an array or a
+string with the captured command output (depending on the original function
+call context; C<command_noisy()> returns C<undef>) and $<cmdline> which
+returns the command and its arguments (but without proper quoting).
+
+Note that the C<command_*_pipe()> functions cannot throw this exception since
+it has no idea whether the command failed or not. You will only find out
+at the time you C<close> the pipe; if you want to have that automated,
+use C<command_close_pipe()>, which can throw the exception.
+
+=cut
+
+{
+ package Git::Error::Command;
+
+ @Git::Error::Command::ISA = qw(Error);
+
+ sub new {
+ my $self = shift;
+ my $cmdline = '' . shift;
+ my $value = 0 + shift;
+ my $outputref = shift;
+ my(@args) = ();
+
+ local $Error::Depth = $Error::Depth + 1;
+
+ push(@args, '-cmdline', $cmdline);
+ push(@args, '-value', $value);
+ push(@args, '-outputref', $outputref);
+
+ $self->SUPER::new(-text => 'command returned error', @args);
+ }
+
+ sub stringify {
+ my $self = shift;
+ my $text = $self->SUPER::stringify;
+ $self->cmdline() . ': ' . $text . ': ' . $self->value() . "\n";
+ }
+
+ sub cmdline {
+ my $self = shift;
+ $self->{'-cmdline'};
+ }
+
+ sub cmd_output {
+ my $self = shift;
+ my $ref = $self->{'-outputref'};
+ defined $ref or undef;
+ if (ref $ref eq 'ARRAY') {
+ return @$ref;
+ } else { # SCALAR
+ return $$ref;
+ }
+ }
+}
+
+=over 4
+
+=item git_cmd_try { CODE } ERRMSG
+
+This magical statement will automatically catch any C<Git::Error::Command>
+exceptions thrown by C<CODE> and make your program die with C<ERRMSG>
+on its lips; the message will have %s substituted for the command line
+and %d for the exit status. This statement is useful mostly for producing
+more user-friendly error messages.
+
+In case of no exception caught the statement returns C<CODE>'s return value.
+
+Note that this is the only auto-exported function.
+
+=cut
+
+sub git_cmd_try(&$) {
+ my ($code, $errmsg) = @_;
+ my @result;
+ my $err;
+ my $array = wantarray;
+ try {
+ if ($array) {
+ @result = &$code;
+ } else {
+ $result[0] = &$code;
+ }
+ } catch Git::Error::Command with {
+ my $E = shift;
+ $err = $errmsg;
+ $err =~ s/\%s/$E->cmdline()/ge;
+ $err =~ s/\%d/$E->value()/ge;
+ # We can't croak here since Error.pm would mangle
+ # that to Error::Simple.
+ };
+ $err and croak $err;
+ return $array ? @result : $result[0];
+}
+
+
+=back
+
+=head1 COPYRIGHT
+
+Copyright 2006 by Petr Baudis E<lt>pasky@suse.czE<gt>.
+
+This module is free software; it may be used, copied, modified
+and distributed under the terms of the GNU General Public Licence,
+either version 2, or (at your option) any later version.
+
+=cut
+
+
+# Take raw method argument list and return ($obj, @args) in case
+# the method was called upon an instance and (undef, @args) if
+# it was called directly.
+sub _maybe_self {
+ # This breaks inheritance. Oh well.
+ ref $_[0] eq 'Git' ? @_ : (undef, @_);
+}
+
+# Check if the command id is something reasonable.
+sub _check_valid_cmd {
+ my ($cmd) = @_;
+ $cmd =~ /^[a-z0-9A-Z_-]+$/ or throw Error::Simple("bad command: $cmd");
+}
+
+# Common backend for the pipe creators.
+sub _command_common_pipe {
+ my $direction = shift;
+ my ($self, @p) = _maybe_self(@_);
+ my (%opts, $cmd, @args);
+ if (ref $p[0]) {
+ ($cmd, @args) = @{shift @p};
+ %opts = ref $p[0] ? %{$p[0]} : @p;
+ } else {
+ ($cmd, @args) = @p;
+ }
+ _check_valid_cmd($cmd);
+
+ my $fh;
+ if ($^O eq '##INSERT_ACTIVESTATE_STRING_HERE##') {
+ # ActiveState Perl
+ #defined $opts{STDERR} and
+ # warn 'ignoring STDERR option - running w/ ActiveState';
+ $direction eq '-|' or
+ die 'input pipe for ActiveState not implemented';
+ tie ($fh, 'Git::activestate_pipe', $cmd, @args);
+
+ } else {
+ my $pid = open($fh, $direction);
+ if (not defined $pid) {
+ throw Error::Simple("open failed: $!");
+ } elsif ($pid == 0) {
+ if (defined $opts{STDERR}) {
+ close STDERR;
+ }
+ if ($opts{STDERR}) {
+ open (STDERR, '>&', $opts{STDERR})
+ or die "dup failed: $!";
+ }
+ _cmd_exec($self, $cmd, @args);
+ }
+ }
+ return wantarray ? ($fh, join(' ', $cmd, @args)) : $fh;
+}
+
+# When already in the subprocess, set up the appropriate state
+# for the given repository and execute the git command.
+sub _cmd_exec {
+ my ($self, @args) = @_;
+ if ($self) {
+ $self->repo_path() and $ENV{'GIT_DIR'} = $self->repo_path();
+ $self->wc_path() and chdir($self->wc_path());
+ $self->wc_subdir() and chdir($self->wc_subdir());
+ }
+ _execv_git_cmd(@args);
+ die "exec failed: $!";
+}
+
+# Execute the given Git command ($_[0]) with arguments ($_[1..])
+# by searching for it at proper places.
+sub _execv_git_cmd { exec('git', @_); }
+
+# Close pipe to a subprocess.
+sub _cmd_close {
+ my ($fh, $ctx) = @_;
+ if (not close $fh) {
+ if ($!) {
+ # It's just close, no point in fatalities
+ carp "error closing pipe: $!";
+ } elsif ($? >> 8) {
+ # The caller should pepper this.
+ throw Git::Error::Command($ctx, $? >> 8);
+ }
+ # else we might e.g. closed a live stream; the command
+ # dying of SIGPIPE would drive us here.
+ }
+}
+
+
+sub DESTROY { }
+
+
+# Pipe implementation for ActiveState Perl.
+
+package Git::activestate_pipe;
+use strict;
+
+sub TIEHANDLE {
+ my ($class, @params) = @_;
+ # FIXME: This is probably horrible idea and the thing will explode
+ # at the moment you give it arguments that require some quoting,
+ # but I have no ActiveState clue... --pasky
+ my $cmdline = join " ", @params;
+ my @data = qx{$cmdline};
+ bless { i => 0, data => \@data }, $class;
+}
+
+sub READLINE {
+ my $self = shift;
+ if ($self->{i} >= scalar @{$self->{data}}) {
+ return undef;
+ }
+ return $self->{'data'}->[ $self->{i}++ ];
+}
+
+sub CLOSE {
+ my $self = shift;
+ delete $self->{data};
+ delete $self->{i};
+}
+
+sub EOF {
+ my $self = shift;
+ return ($self->{i} >= scalar @{$self->{data}});
+}
+
+
+1; # Famous last words
--- /dev/null
+use ExtUtils::MakeMaker;
+
+sub MY::postamble {
+ return <<'MAKE_FRAG';
+instlibdir:
+ @echo '$(INSTALLSITELIB)'
+
+MAKE_FRAG
+}
+
+my %pm = ('Git.pm' => '$(INST_LIBDIR)/Git.pm');
+
+# We come with our own bundled Error.pm. It's not in the set of default
+# Perl modules so install it if it's not available on the system yet.
+eval { require Error };
+if ($@) {
+ $pm{'private-Error.pm'} = '$(INST_LIBDIR)/Error.pm';
+}
+
+my %extra;
+$extra{DESTDIR} = $ENV{DESTDIR} if $ENV{DESTDIR};
+
+WriteMakefile(
+ NAME => 'Git',
+ VERSION_FROM => 'Git.pm',
+ PM => \%pm,
+ %extra
+);
--- /dev/null
+# Error.pm
+#
+# Copyright (c) 1997-8 Graham Barr <gbarr@ti.com>. All rights reserved.
+# This program is free software; you can redistribute it and/or
+# modify it under the same terms as Perl itself.
+#
+# Based on my original Error.pm, and Exceptions.pm by Peter Seibel
+# <peter@weblogic.com> and adapted by Jesse Glick <jglick@sig.bsh.com>.
+#
+# but modified ***significantly***
+
+package Error;
+
+use strict;
+use vars qw($VERSION);
+use 5.004;
+
+$VERSION = "0.15009";
+
+use overload (
+ '""' => 'stringify',
+ '0+' => 'value',
+ 'bool' => sub { return 1; },
+ 'fallback' => 1
+);
+
+$Error::Depth = 0; # Depth to pass to caller()
+$Error::Debug = 0; # Generate verbose stack traces
+@Error::STACK = (); # Clause stack for try
+$Error::THROWN = undef; # last error thrown, a workaround until die $ref works
+
+my $LAST; # Last error created
+my %ERROR; # Last error associated with package
+
+sub throw_Error_Simple
+{
+ my $args = shift;
+ return Error::Simple->new($args->{'text'});
+}
+
+$Error::ObjectifyCallback = \&throw_Error_Simple;
+
+
+# Exported subs are defined in Error::subs
+
+sub import {
+ shift;
+ local $Exporter::ExportLevel = $Exporter::ExportLevel + 1;
+ Error::subs->import(@_);
+}
+
+# I really want to use last for the name of this method, but it is a keyword
+# which prevent the syntax last Error
+
+sub prior {
+ shift; # ignore
+
+ return $LAST unless @_;
+
+ my $pkg = shift;
+ return exists $ERROR{$pkg} ? $ERROR{$pkg} : undef
+ unless ref($pkg);
+
+ my $obj = $pkg;
+ my $err = undef;
+ if($obj->isa('HASH')) {
+ $err = $obj->{'__Error__'}
+ if exists $obj->{'__Error__'};
+ }
+ elsif($obj->isa('GLOB')) {
+ $err = ${*$obj}{'__Error__'}
+ if exists ${*$obj}{'__Error__'};
+ }
+
+ $err;
+}
+
+sub flush {
+ shift; #ignore
+
+ unless (@_) {
+ $LAST = undef;
+ return;
+ }
+
+ my $pkg = shift;
+ return unless ref($pkg);
+
+ undef $ERROR{$pkg} if defined $ERROR{$pkg};
+}
+
+# Return as much information as possible about where the error
+# happened. The -stacktrace element only exists if $Error::DEBUG
+# was set when the error was created
+
+sub stacktrace {
+ my $self = shift;
+
+ return $self->{'-stacktrace'}
+ if exists $self->{'-stacktrace'};
+
+ my $text = exists $self->{'-text'} ? $self->{'-text'} : "Died";
+
+ $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+ unless($text =~ /\n$/s);
+
+ $text;
+}
+
+# Allow error propagation, ie
+#
+# $ber->encode(...) or
+# return Error->prior($ber)->associate($ldap);
+
+sub associate {
+ my $err = shift;
+ my $obj = shift;
+
+ return unless ref($obj);
+
+ if($obj->isa('HASH')) {
+ $obj->{'__Error__'} = $err;
+ }
+ elsif($obj->isa('GLOB')) {
+ ${*$obj}{'__Error__'} = $err;
+ }
+ $obj = ref($obj);
+ $ERROR{ ref($obj) } = $err;
+
+ return;
+}
+
+sub new {
+ my $self = shift;
+ my($pkg,$file,$line) = caller($Error::Depth);
+
+ my $err = bless {
+ '-package' => $pkg,
+ '-file' => $file,
+ '-line' => $line,
+ @_
+ }, $self;
+
+ $err->associate($err->{'-object'})
+ if(exists $err->{'-object'});
+
+ # To always create a stacktrace would be very inefficient, so
+ # we only do it if $Error::Debug is set
+
+ if($Error::Debug) {
+ require Carp;
+ local $Carp::CarpLevel = $Error::Depth;
+ my $text = defined($err->{'-text'}) ? $err->{'-text'} : "Error";
+ my $trace = Carp::longmess($text);
+ # Remove try calls from the trace
+ $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+ $trace =~ s/(\n\s+\S+__ANON__[^\n]+)?\n\s+eval[^\n]+\n\s+Error::subs::run_clauses[^\n]+\n\s+Error::subs::try[^\n]+(?=\n)//sog;
+ $err->{'-stacktrace'} = $trace
+ }
+
+ $@ = $LAST = $ERROR{$pkg} = $err;
+}
+
+# Throw an error. this contains some very gory code.
+
+sub throw {
+ my $self = shift;
+ local $Error::Depth = $Error::Depth + 1;
+
+ # if we are not rethrow-ing then create the object to throw
+ $self = $self->new(@_) unless ref($self);
+
+ die $Error::THROWN = $self;
+}
+
+# syntactic sugar for
+#
+# die with Error( ... );
+
+sub with {
+ my $self = shift;
+ local $Error::Depth = $Error::Depth + 1;
+
+ $self->new(@_);
+}
+
+# syntactic sugar for
+#
+# record Error( ... ) and return;
+
+sub record {
+ my $self = shift;
+ local $Error::Depth = $Error::Depth + 1;
+
+ $self->new(@_);
+}
+
+# catch clause for
+#
+# try { ... } catch CLASS with { ... }
+
+sub catch {
+ my $pkg = shift;
+ my $code = shift;
+ my $clauses = shift || {};
+ my $catch = $clauses->{'catch'} ||= [];
+
+ unshift @$catch, $pkg, $code;
+
+ $clauses;
+}
+
+# Object query methods
+
+sub object {
+ my $self = shift;
+ exists $self->{'-object'} ? $self->{'-object'} : undef;
+}
+
+sub file {
+ my $self = shift;
+ exists $self->{'-file'} ? $self->{'-file'} : undef;
+}
+
+sub line {
+ my $self = shift;
+ exists $self->{'-line'} ? $self->{'-line'} : undef;
+}
+
+sub text {
+ my $self = shift;
+ exists $self->{'-text'} ? $self->{'-text'} : undef;
+}
+
+# overload methods
+
+sub stringify {
+ my $self = shift;
+ defined $self->{'-text'} ? $self->{'-text'} : "Died";
+}
+
+sub value {
+ my $self = shift;
+ exists $self->{'-value'} ? $self->{'-value'} : undef;
+}
+
+package Error::Simple;
+
+@Error::Simple::ISA = qw(Error);
+
+sub new {
+ my $self = shift;
+ my $text = "" . shift;
+ my $value = shift;
+ my(@args) = ();
+
+ local $Error::Depth = $Error::Depth + 1;
+
+ @args = ( -file => $1, -line => $2)
+ if($text =~ s/\s+at\s+(\S+)\s+line\s+(\d+)(?:,\s*<[^>]*>\s+line\s+\d+)?\.?\n?$//s);
+ push(@args, '-value', 0 + $value)
+ if defined($value);
+
+ $self->SUPER::new(-text => $text, @args);
+}
+
+sub stringify {
+ my $self = shift;
+ my $text = $self->SUPER::stringify;
+ $text .= sprintf(" at %s line %d.\n", $self->file, $self->line)
+ unless($text =~ /\n$/s);
+ $text;
+}
+
+##########################################################################
+##########################################################################
+
+# Inspired by code from Jesse Glick <jglick@sig.bsh.com> and
+# Peter Seibel <peter@weblogic.com>
+
+package Error::subs;
+
+use Exporter ();
+use vars qw(@EXPORT_OK @ISA %EXPORT_TAGS);
+
+@EXPORT_OK = qw(try with finally except otherwise);
+%EXPORT_TAGS = (try => \@EXPORT_OK);
+
+@ISA = qw(Exporter);
+
+
+sub blessed {
+ my $item = shift;
+ local $@; # don't kill an outer $@
+ ref $item and eval { $item->can('can') };
+}
+
+
+sub run_clauses ($$$\@) {
+ my($clauses,$err,$wantarray,$result) = @_;
+ my $code = undef;
+
+ $err = $Error::ObjectifyCallback->({'text' =>$err}) unless ref($err);
+
+ CATCH: {
+
+ # catch
+ my $catch;
+ if(defined($catch = $clauses->{'catch'})) {
+ my $i = 0;
+
+ CATCHLOOP:
+ for( ; $i < @$catch ; $i += 2) {
+ my $pkg = $catch->[$i];
+ unless(defined $pkg) {
+ #except
+ splice(@$catch,$i,2,$catch->[$i+1]->());
+ $i -= 2;
+ next CATCHLOOP;
+ }
+ elsif(blessed($err) && $err->isa($pkg)) {
+ $code = $catch->[$i+1];
+ while(1) {
+ my $more = 0;
+ local($Error::THROWN);
+ my $ok = eval {
+ if($wantarray) {
+ @{$result} = $code->($err,\$more);
+ }
+ elsif(defined($wantarray)) {
+ @{$result} = ();
+ $result->[0] = $code->($err,\$more);
+ }
+ else {
+ $code->($err,\$more);
+ }
+ 1;
+ };
+ if( $ok ) {
+ next CATCHLOOP if $more;
+ undef $err;
+ }
+ else {
+ $err = defined($Error::THROWN)
+ ? $Error::THROWN : $@;
+ $err = $Error::ObjectifyCallback->({'text' =>$err})
+ unless ref($err);
+ }
+ last CATCH;
+ };
+ }
+ }
+ }
+
+ # otherwise
+ my $owise;
+ if(defined($owise = $clauses->{'otherwise'})) {
+ my $code = $clauses->{'otherwise'};
+ my $more = 0;
+ my $ok = eval {
+ if($wantarray) {
+ @{$result} = $code->($err,\$more);
+ }
+ elsif(defined($wantarray)) {
+ @{$result} = ();
+ $result->[0] = $code->($err,\$more);
+ }
+ else {
+ $code->($err,\$more);
+ }
+ 1;
+ };
+ if( $ok ) {
+ undef $err;
+ }
+ else {
+ $err = defined($Error::THROWN)
+ ? $Error::THROWN : $@;
+
+ $err = $Error::ObjectifyCallback->({'text' =>$err})
+ unless ref($err);
+ }
+ }
+ }
+ $err;
+}
+
+sub try (&;$) {
+ my $try = shift;
+ my $clauses = @_ ? shift : {};
+ my $ok = 0;
+ my $err = undef;
+ my @result = ();
+
+ unshift @Error::STACK, $clauses;
+
+ my $wantarray = wantarray();
+
+ do {
+ local $Error::THROWN = undef;
+ local $@ = undef;
+
+ $ok = eval {
+ if($wantarray) {
+ @result = $try->();
+ }
+ elsif(defined $wantarray) {
+ $result[0] = $try->();
+ }
+ else {
+ $try->();
+ }
+ 1;
+ };
+
+ $err = defined($Error::THROWN) ? $Error::THROWN : $@
+ unless $ok;
+ };
+
+ shift @Error::STACK;
+
+ $err = run_clauses($clauses,$err,wantarray,@result)
+ unless($ok);
+
+ $clauses->{'finally'}->()
+ if(defined($clauses->{'finally'}));
+
+ if (defined($err))
+ {
+ if (blessed($err) && $err->can('throw'))
+ {
+ throw $err;
+ }
+ else
+ {
+ die $err;
+ }
+ }
+
+ wantarray ? @result : $result[0];
+}
+
+# Each clause adds a sub to the list of clauses. The finally clause is
+# always the last, and the otherwise clause is always added just before
+# the finally clause.
+#
+# All clauses, except the finally clause, add a sub which takes one argument
+# this argument will be the error being thrown. The sub will return a code ref
+# if that clause can handle that error, otherwise undef is returned.
+#
+# The otherwise clause adds a sub which unconditionally returns the users
+# code reference, this is why it is forced to be last.
+#
+# The catch clause is defined in Error.pm, as the syntax causes it to
+# be called as a method
+
+sub with (&;$) {
+ @_
+}
+
+sub finally (&) {
+ my $code = shift;
+ my $clauses = { 'finally' => $code };
+ $clauses;
+}
+
+# The except clause is a block which returns a hashref or a list of
+# key-value pairs, where the keys are the classes and the values are subs.
+
+sub except (&;$) {
+ my $code = shift;
+ my $clauses = shift || {};
+ my $catch = $clauses->{'catch'} ||= [];
+
+ my $sub = sub {
+ my $ref;
+ my(@array) = $code->($_[0]);
+ if(@array == 1 && ref($array[0])) {
+ $ref = $array[0];
+ $ref = [ %$ref ]
+ if(UNIVERSAL::isa($ref,'HASH'));
+ }
+ else {
+ $ref = \@array;
+ }
+ @$ref
+ };
+
+ unshift @{$catch}, undef, $sub;
+
+ $clauses;
+}
+
+sub otherwise (&;$) {
+ my $code = shift;
+ my $clauses = shift || {};
+
+ if(exists $clauses->{'otherwise'}) {
+ require Carp;
+ Carp::croak("Multiple otherwise clauses");
+ }
+
+ $clauses->{'otherwise'} = $code;
+
+ $clauses;
+}
+
+1;
+__END__
+
+=head1 NAME
+
+Error - Error/exception handling in an OO-ish way
+
+=head1 SYNOPSIS
+
+ use Error qw(:try);
+
+ throw Error::Simple( "A simple error");
+
+ sub xyz {
+ ...
+ record Error::Simple("A simple error")
+ and return;
+ }
+
+ unlink($file) or throw Error::Simple("$file: $!",$!);
+
+ try {
+ do_some_stuff();
+ die "error!" if $condition;
+ throw Error::Simple -text => "Oops!" if $other_condition;
+ }
+ catch Error::IO with {
+ my $E = shift;
+ print STDERR "File ", $E->{'-file'}, " had a problem\n";
+ }
+ except {
+ my $E = shift;
+ my $general_handler=sub {send_message $E->{-description}};
+ return {
+ UserException1 => $general_handler,
+ UserException2 => $general_handler
+ };
+ }
+ otherwise {
+ print STDERR "Well I don't know what to say\n";
+ }
+ finally {
+ close_the_garage_door_already(); # Should be reliable
+ }; # Don't forget the trailing ; or you might be surprised
+
+=head1 DESCRIPTION
+
+The C<Error> package provides two interfaces. Firstly C<Error> provides
+a procedural interface to exception handling. Secondly C<Error> is a
+base class for errors/exceptions that can either be thrown, for
+subsequent catch, or can simply be recorded.
+
+Errors in the class C<Error> should not be thrown directly, but the
+user should throw errors from a sub-class of C<Error>.
+
+=head1 PROCEDURAL INTERFACE
+
+C<Error> exports subroutines to perform exception handling. These will
+be exported if the C<:try> tag is used in the C<use> line.
+
+=over 4
+
+=item try BLOCK CLAUSES
+
+C<try> is the main subroutine called by the user. All other subroutines
+exported are clauses to the try subroutine.
+
+The BLOCK will be evaluated and, if no error is throw, try will return
+the result of the block.
+
+C<CLAUSES> are the subroutines below, which describe what to do in the
+event of an error being thrown within BLOCK.
+
+=item catch CLASS with BLOCK
+
+This clauses will cause all errors that satisfy C<$err-E<gt>isa(CLASS)>
+to be caught and handled by evaluating C<BLOCK>.
+
+C<BLOCK> will be passed two arguments. The first will be the error
+being thrown. The second is a reference to a scalar variable. If this
+variable is set by the catch block then, on return from the catch
+block, try will continue processing as if the catch block was never
+found.
+
+To propagate the error the catch block may call C<$err-E<gt>throw>
+
+If the scalar reference by the second argument is not set, and the
+error is not thrown. Then the current try block will return with the
+result from the catch block.
+
+=item except BLOCK
+
+When C<try> is looking for a handler, if an except clause is found
+C<BLOCK> is evaluated. The return value from this block should be a
+HASHREF or a list of key-value pairs, where the keys are class names
+and the values are CODE references for the handler of errors of that
+type.
+
+=item otherwise BLOCK
+
+Catch any error by executing the code in C<BLOCK>
+
+When evaluated C<BLOCK> will be passed one argument, which will be the
+error being processed.
+
+Only one otherwise block may be specified per try block
+
+=item finally BLOCK
+
+Execute the code in C<BLOCK> either after the code in the try block has
+successfully completed, or if the try block throws an error then
+C<BLOCK> will be executed after the handler has completed.
+
+If the handler throws an error then the error will be caught, the
+finally block will be executed and the error will be re-thrown.
+
+Only one finally block may be specified per try block
+
+=back
+
+=head1 CLASS INTERFACE
+
+=head2 CONSTRUCTORS
+
+The C<Error> object is implemented as a HASH. This HASH is initialized
+with the arguments that are passed to it's constructor. The elements
+that are used by, or are retrievable by the C<Error> class are listed
+below, other classes may add to these.
+
+ -file
+ -line
+ -text
+ -value
+ -object
+
+If C<-file> or C<-line> are not specified in the constructor arguments
+then these will be initialized with the file name and line number where
+the constructor was called from.
+
+If the error is associated with an object then the object should be
+passed as the C<-object> argument. This will allow the C<Error> package
+to associate the error with the object.
+
+The C<Error> package remembers the last error created, and also the
+last error associated with a package. This could either be the last
+error created by a sub in that package, or the last error which passed
+an object blessed into that package as the C<-object> argument.
+
+=over 4
+
+=item throw ( [ ARGS ] )
+
+Create a new C<Error> object and throw an error, which will be caught
+by a surrounding C<try> block, if there is one. Otherwise it will cause
+the program to exit.
+
+C<throw> may also be called on an existing error to re-throw it.
+
+=item with ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+ die with Some::Error ( ... );
+
+=item record ( [ ARGS ] )
+
+Create a new C<Error> object and returns it. This is defined for
+syntactic sugar, eg
+
+ record Some::Error ( ... )
+ and return;
+
+=back
+
+=head2 STATIC METHODS
+
+=over 4
+
+=item prior ( [ PACKAGE ] )
+
+Return the last error created, or the last error associated with
+C<PACKAGE>
+
+=item flush ( [ PACKAGE ] )
+
+Flush the last error created, or the last error associated with
+C<PACKAGE>.It is necessary to clear the error stack before exiting the
+package or uncaught errors generated using C<record> will be reported.
+
+ $Error->flush;
+
+=cut
+
+=back
+
+=head2 OBJECT METHODS
+
+=over 4
+
+=item stacktrace
+
+If the variable C<$Error::Debug> was non-zero when the error was
+created, then C<stacktrace> returns a string created by calling
+C<Carp::longmess>. If the variable was zero the C<stacktrace> returns
+the text of the error appended with the filename and line number of
+where the error was created, providing the text does not end with a
+newline.
+
+=item object
+
+The object this error was associated with
+
+=item file
+
+The file where the constructor of this error was called from
+
+=item line
+
+The line where the constructor of this error was called from
+
+=item text
+
+The text of the error
+
+=back
+
+=head2 OVERLOAD METHODS
+
+=over 4
+
+=item stringify
+
+A method that converts the object into a string. This method may simply
+return the same as the C<text> method, or it may append more
+information. For example the file name and line number.
+
+By default this method returns the C<-text> argument that was passed to
+the constructor, or the string C<"Died"> if none was given.
+
+=item value
+
+A method that will return a value that can be associated with the
+error. For example if an error was created due to the failure of a
+system call, then this may return the numeric value of C<$!> at the
+time.
+
+By default this method returns the C<-value> argument that was passed
+to the constructor.
+
+=back
+
+=head1 PRE-DEFINED ERROR CLASSES
+
+=over 4
+
+=item Error::Simple
+
+This class can be used to hold simple error strings and values. It's
+constructor takes two arguments. The first is a text value, the second
+is a numeric value. These values are what will be returned by the
+overload methods.
+
+If the text value ends with C<at file line 1> as $@ strings do, then
+this infomation will be used to set the C<-file> and C<-line> arguments
+of the error object.
+
+This class is used internally if an eval'd block die's with an error
+that is a plain string. (Unless C<$Error::ObjectifyCallback> is modified)
+
+=back
+
+=head1 $Error::ObjectifyCallback
+
+This variable holds a reference to a subroutine that converts errors that
+are plain strings to objects. It is used by Error.pm to convert textual
+errors to objects, and can be overrided by the user.
+
+It accepts a single argument which is a hash reference to named parameters.
+Currently the only named parameter passed is C<'text'> which is the text
+of the error, but others may be available in the future.
+
+For example the following code will cause Error.pm to throw objects of the
+class MyError::Bar by default:
+
+ sub throw_MyError_Bar
+ {
+ my $args = shift;
+ my $err = MyError::Bar->new();
+ $err->{'MyBarText'} = $args->{'text'};
+ return $err;
+ }
+
+ {
+ local $Error::ObjectifyCallback = \&throw_MyError_Bar;
+
+ # Error handling here.
+ }
+
+=head1 KNOWN BUGS
+
+None, but that does not mean there are not any.
+
+=head1 AUTHORS
+
+Graham Barr <gbarr@pobox.com>
+
+The code that inspired me to write this was originally written by
+Peter Seibel <peter@weblogic.com> and adapted by Jesse Glick
+<jglick@sig.bsh.com>.
+
+=head1 MAINTAINER
+
+Shlomi Fish <shlomif@iglu.org.il>
+
+=head1 PAST MAINTAINERS
+
+Arun Kumar U <u_arunkumar@yahoo.com>
+
+=cut
}
}
+ /* Is it a directory? */
+ if (S_ISDIR(st.st_mode)) {
+ errno = EISDIR;
+ return NULL;
+ }
+
/*
* Anything else, just open it and try to use it as
* a ref
int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long size, const char *type)
{
- char header[100];
unsigned char real_sha1[20];
- SHA_CTX c;
-
- SHA1_Init(&c);
- SHA1_Update(&c, header, 1+sprintf(header, "%s %lu", type, size));
- SHA1_Update(&c, map, size);
- SHA1_Final(real_sha1, &c);
+ hash_sha1_file(map, size, type, real_sha1);
return hashcmp(sha1, real_sha1) ? -1 : 0;
}
if (sizep) {
const unsigned char *data;
- unsigned char delta_head[64];
+ unsigned char delta_head[20];
unsigned long result_size;
z_stream stream;
int st;
}
}
-char *write_sha1_file_prepare(void *buf,
- unsigned long len,
- const char *type,
- unsigned char *sha1,
- unsigned char *hdr,
- int *hdrlen)
+static void write_sha1_file_prepare(void *buf, unsigned long len,
+ const char *type, unsigned char *sha1,
+ unsigned char *hdr, int *hdrlen)
{
SHA_CTX c;
SHA1_Update(&c, hdr, *hdrlen);
SHA1_Update(&c, buf, len);
SHA1_Final(sha1, &c);
-
- return sha1_file_name(sha1);
}
/*
stream->avail_out -= hdr;
}
+int hash_sha1_file(void *buf, unsigned long len, const char *type,
+ unsigned char *sha1)
+{
+ unsigned char hdr[50];
+ int hdrlen;
+ write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ return 0;
+}
+
int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned char *returnsha1)
{
int size;
/* Normally if we have it in the pack then we do not bother writing
* it out into .git/objects/??/?{38} file.
*/
- filename = write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ write_sha1_file_prepare(buf, len, type, sha1, hdr, &hdrlen);
+ filename = sha1_file_name(sha1);
if (returnsha1)
hashcpy(returnsha1, sha1);
if (has_sha1_file(sha1))
unsigned long size = 4096;
char *buf = xmalloc(size);
int ret;
- unsigned char hdr[50];
- int hdrlen;
if (read_pipe(fd, &buf, &size)) {
free(buf);
type = blob_type;
if (write_object)
ret = write_sha1_file(buf, size, type, sha1);
- else {
- write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
- ret = 0;
- }
+ else
+ ret = hash_sha1_file(buf, size, type, sha1);
free(buf);
return ret;
}
unsigned long size = st->st_size;
void *buf;
int ret;
- unsigned char hdr[50];
- int hdrlen;
buf = "";
if (size)
type = blob_type;
if (write_object)
ret = write_sha1_file(buf, size, type, sha1);
- else {
- write_sha1_file_prepare(buf, size, type, sha1, hdr, &hdrlen);
- ret = 0;
- }
+ else
+ ret = hash_sha1_file(buf, size, type, sha1);
if (size)
munmap(buf, size);
return ret;
return error("readlink(\"%s\"): %s", path,
errstr);
}
- if (!write_object) {
- unsigned char hdr[50];
- int hdrlen;
- write_sha1_file_prepare(target, st->st_size, blob_type,
- sha1, hdr, &hdrlen);
- } else if (write_sha1_file(target, st->st_size, blob_type, sha1))
+ if (!write_object)
+ hash_sha1_file(target, st->st_size, blob_type, sha1);
+ else if (write_sha1_file(target, st->st_size, blob_type, sha1))
return error("%s: failed to insert into database",
path);
free(target);
char canonical[40];
unsigned char res[20];
- if (len < MINIMUM_ABBREV)
+ if (len < MINIMUM_ABBREV || len > 40)
return -1;
hashclr(res);
memset(canonical, 'x', 40);
* stream, aka "verbose"). A message over band #3 is a signal that
* the remote died unexpectedly. A flush() concludes the stream.
*/
-int recv_sideband(const char *me, int in_stream, int out, int err, char *buf, int bufsz)
+int recv_sideband(const char *me, int in_stream, int out, int err)
{
+ char buf[7 + LARGE_PACKET_MAX + 1];
+ strcpy(buf, "remote:");
while (1) {
- int len = packet_read_line(in_stream, buf, bufsz);
+ int band, len;
+ len = packet_read_line(in_stream, buf+7, LARGE_PACKET_MAX);
if (len == 0)
break;
if (len < 1) {
safe_write(err, buf, len);
return SIDEBAND_PROTOCOL_ERROR;
}
+ band = buf[7] & 0xff;
len--;
- switch (buf[0] & 0xFF) {
+ switch (band) {
case 3:
- safe_write(err, "remote: ", 8);
- safe_write(err, buf+1, len);
- safe_write(err, "\n", 1);
+ buf[7] = ' ';
+ buf[8+len] = '\n';
+ safe_write(err, buf, 8+len+1);
return SIDEBAND_REMOTE_ERROR;
case 2:
- safe_write(err, "remote: ", 8);
- safe_write(err, buf+1, len);
+ buf[7] = ' ';
+ safe_write(err, buf, 8+len);
continue;
case 1:
- safe_write(out, buf+1, len);
+ safe_write(out, buf+8, len);
continue;
default:
- len = sprintf(buf + 1,
+ len = sprintf(buf,
"%s: protocol error: bad band #%d\n",
- me, buf[0] & 0xFF);
- safe_write(err, buf+1, len);
+ me, band);
+ safe_write(err, buf, len);
return SIDEBAND_PROTOCOL_ERROR;
}
}
#define DEFAULT_PACKET_MAX 1000
#define LARGE_PACKET_MAX 65520
-int recv_sideband(const char *me, int in_stream, int out, int err, char *, int);
+int recv_sideband(const char *me, int in_stream, int out, int err);
ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
#endif
# Copyright (c) 2005 Johannes Schindelin
#
-test_description='Test git-rev-parse with different parent options'
+test_description='A simple turial in the form of a test case'
. ./test-lib.sh
--- /dev/null
+#!/bin/sh
+#
+# Copyright (c) 2006 Johannes E. Schindelin
+#
+
+test_description='Test special whitespace in diff engine.
+
+'
+. ./test-lib.sh
+. ../diff-lib.sh
+
+# Ray Lehtiniemi's example
+
+cat << EOF > x
+do {
+ nothing;
+} while (0);
+EOF
+
+git-update-index --add x
+
+cat << EOF > x
+do
+{
+ nothing;
+}
+while (0);
+EOF
+
+cat << EOF > expect
+diff --git a/x b/x
+index adf3937..6edc172 100644
+--- a/x
++++ b/x
+@@ -1,3 +1,5 @@
+-do {
++do
++{
+ nothing;
+-} while (0);
++}
++while (0);
+EOF
+
+git-diff > out
+test_expect_success "Ray's example without options" 'diff -u expect out'
+
+git-diff -w > out
+test_expect_success "Ray's example with -w" 'diff -u expect out'
+
+git-diff -b > out
+test_expect_success "Ray's example with -b" 'diff -u expect out'
+
+tr 'Q' '\015' << EOF > x
+whitespace at beginning
+whitespace change
+whitespace in the middle
+whitespace at end
+unchanged line
+CR at endQ
+EOF
+
+git-update-index x
+
+cat << EOF > x
+ whitespace at beginning
+whitespace change
+white space in the middle
+whitespace at end
+unchanged line
+CR at end
+EOF
+
+tr 'Q' '\015' << EOF > expect
+diff --git a/x b/x
+index d99af23..8b32fb5 100644
+--- a/x
++++ b/x
+@@ -1,6 +1,6 @@
+-whitespace at beginning
+-whitespace change
+-whitespace in the middle
+-whitespace at end
++ whitespace at beginning
++whitespace change
++white space in the middle
++whitespace at end
+ unchanged line
+-CR at endQ
++CR at end
+EOF
+git-diff > out
+test_expect_success 'another test, without options' 'diff -u expect out'
+
+cat << EOF > expect
+diff --git a/x b/x
+index d99af23..8b32fb5 100644
+EOF
+git-diff -w > out
+test_expect_success 'another test, with -w' 'diff -u expect out'
+
+tr 'Q' '\015' << EOF > expect
+diff --git a/x b/x
+index d99af23..8b32fb5 100644
+--- a/x
++++ b/x
+@@ -1,6 +1,6 @@
+-whitespace at beginning
++ whitespace at beginning
+ whitespace change
+-whitespace in the middle
+-whitespace at end
++white space in the middle
++whitespace at end
+ unchanged line
+-CR at endQ
++CR at end
+EOF
+git-diff -b > out
+test_expect_success 'another test, with -b' 'diff -u expect out'
+
+test_done
. ./test-lib.sh
TAR=${TAR:-tar}
+UNZIP=${UNZIP:-unzip}
test_expect_success \
'populate workdir' \
'validate file contents with prefix' \
'diff -r a c/prefix/a'
+test_expect_success \
+ 'git-archive --format=zip' \
+ 'git-archive --format=zip HEAD >d.zip'
+
+test_expect_success \
+ 'extract ZIP archive' \
+ '(mkdir d && cd d && $UNZIP ../d.zip)'
+
+test_expect_success \
+ 'validate filenames' \
+ '(cd d/a && find .) | sort >d.lst &&
+ diff a.lst d.lst'
+
+test_expect_success \
+ 'validate file contents' \
+ 'diff -r a d/a'
+
+test_expect_success \
+ 'git-archive --format=zip with prefix' \
+ 'git-archive --format=zip --prefix=prefix/ HEAD >e.zip'
+
+test_expect_success \
+ 'extract ZIP archive with prefix' \
+ '(mkdir e && cd e && $UNZIP ../e.zip)'
+
+test_expect_success \
+ 'validate filenames with prefix' \
+ '(cd e/prefix/a && find .) | sort >e.lst &&
+ diff a.lst e.lst'
+
+test_expect_success \
+ 'validate file contents with prefix' \
+ 'diff -r a e/prefix/a'
+
test_done
# clone doesn't like it if there is no HEAD. Is that a bug?
(cd foo && touch file && git add file && git commit -m 'add file' >/dev/null 2>&1)
+# source repository given to git-clone should be relative to the
+# current path not to the target dir
+test_expect_failure \
+ 'clone of non-existent (relative to $PWD) source should fail' \
+ 'git-clone ../foo baz'
+
test_expect_success \
'clone should work now that source exists' \
'git-clone foo bar'
'move into "."' \
'git-mv path1/path2/ .'
+test_expect_success "Michael Cassar's test case" '
+ rm -fr .git papers partA &&
+ git init-db &&
+ mkdir -p papers/unsorted papers/all-papers partA &&
+ echo a > papers/unsorted/Thesis.pdf &&
+ echo b > partA/outline.txt &&
+ echo c > papers/unsorted/_another &&
+ git add papers partA &&
+ T1=`git write-tree` &&
+
+ git mv papers/unsorted/Thesis.pdf papers/all-papers/moo-blah.pdf &&
+
+ T=`git write-tree` &&
+ git ls-tree -r $T | grep partA/outline.txt || {
+ git ls-tree -r $T
+ (exit 1)
+ }
+'
+
test_done
else
test_failure_ "$@"
fi
+ echo >&3 ""
}
test_expect_success () {
else
test_failure_ "$@"
fi
+ echo >&3 ""
}
test_expect_code () {
else
test_failure_ "$@"
fi
+ echo >&3 ""
}
# Most tests can use the created repository, but some amy need to create more.
PYTHONPATH=$(pwd)/../compat
export PYTHONPATH
}
+GITPERLLIB=$(pwd)/../perl/blib/lib:$(pwd)/../perl/blib/arch/auto/Git
+export GITPERLLIB
test -d ../templates/blt || {
error "You haven't built things yet, have you?"
}
{
char *trace = getenv("GIT_TRACE");
- if (!trace || !strcmp(trace, "0") || !strcasecmp(trace, "false"))
+ if (!trace || !strcmp(trace, "") ||
+ !strcmp(trace, "0") || !strcasecmp(trace, "false"))
return 0;
if (!strcmp(trace, "1") || !strcasecmp(trace, "true"))
return STDERR_FILENO;
#define XMACROS_H
-#define GR_PRIME 0x9e370001UL
#define XDL_MIN(a, b) ((a) < (b) ? (a): (b))
#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
-#define XDL_HASHLONG(v, b) (((unsigned long)(v) * GR_PRIME) >> ((CHAR_BIT * sizeof(unsigned long)) - (b)))
+#define XDL_ADDBITS(v,b) ((v) + ((v) >> (b)))
+#define XDL_MASKBITS(b) ((1UL << (b)) - 1)
+#define XDL_HASHLONG(v,b) (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
#define XDL_LE32_PUT(p, v) \
do { \
int i1, i2;
if (flags & XDF_IGNORE_WHITESPACE) {
- for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) {
+ for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
if (isspace(l1[i1]))
while (isspace(l1[i1]) && i1 < s1)
i1++;
- else if (isspace(l2[i2]))
+ if (isspace(l2[i2]))
while (isspace(l2[i2]) && i2 < s2)
i2++;
- else if (l1[i1] != l2[i2])
- return l2[i2] - l1[i1];
+ if (i1 < s1 && i2 < s2 && l1[i1++] != l2[i2++])
+ return 0;
}
- if (i1 >= s1)
- return 1;
- else if (i2 >= s2)
- return -1;
+ return (i1 >= s1 && i2 >= s2);
} else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
- for (i1 = i2 = 0; i1 < s1 && i2 < s2; i1++, i2++) {
+ for (i1 = i2 = 0; i1 < s1 && i2 < s2; ) {
if (isspace(l1[i1])) {
if (!isspace(l2[i2]))
- return -1;
+ return 0;
while (isspace(l1[i1]) && i1 < s1)
i1++;
while (isspace(l2[i2]) && i2 < s2)
i2++;
- } else if (l1[i1] != l2[i2])
- return l2[i2] - l1[i1];
+ } else if (l1[i1++] != l2[i2++])
+ return 0;
}
- if (i1 >= s1)
- return 1;
- else if (i2 >= s2)
- return -1;
+ return (i1 >= s1 && i2 >= s2);
} else
return s1 == s2 && !memcmp(l1, l2, s1);
for (; ptr < top && *ptr != '\n'; ptr++) {
if (isspace(*ptr) && (flags & XDF_WHITESPACE_FLAGS)) {
- while (ptr < top && isspace(*ptr) && ptr[1] != '\n')
+ while (ptr + 1 < top && isspace(ptr[1])
+ && ptr[1] != '\n')
ptr++;
if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
ha += (ha << 5);