summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/git-am.txt9
-rw-r--r--Documentation/git-apply.txt30
-rw-r--r--Documentation/git-cvsserver.txt24
-rw-r--r--Documentation/git-read-tree.txt15
-rw-r--r--Documentation/git-repo-config.txt1
-rw-r--r--Documentation/git-rev-list.txt59
-rw-r--r--Documentation/git-svnimport.txt6
-rw-r--r--Documentation/git-tools.txt97
-rwxr-xr-xGIT-VERSION-GEN5
-rw-r--r--INSTALL4
-rw-r--r--Makefile19
-rw-r--r--apply.c160
-rw-r--r--blame.c562
-rw-r--r--cache.h5
-rw-r--r--cat-file.c119
-rwxr-xr-xcontrib/git-svn/git-svn.perl282
-rw-r--r--contrib/git-svn/git-svn.txt52
-rw-r--r--contrib/git-svn/t/t0000-contrib-git-svn.sh36
-rwxr-xr-xcontrib/gitview/gitview7
-rw-r--r--count-delta.c72
-rw-r--r--count-delta.h10
-rw-r--r--diff-delta.c253
-rw-r--r--diffcore-break.c45
-rw-r--r--diffcore-delta.c157
-rw-r--r--diffcore.h4
-rw-r--r--environment.c1
-rw-r--r--epoch.c639
-rw-r--r--epoch.h21
-rwxr-xr-xgit-am.sh16
-rwxr-xr-xgit-annotate.perl22
-rwxr-xr-xgit-archimport.perl4
-rwxr-xr-xgit-branch.sh10
-rwxr-xr-xgit-commit.sh45
-rwxr-xr-xgit-cvsserver.perl197
-rwxr-xr-xgit-mv.perl41
-rwxr-xr-xgit-send-email.perl2
-rwxr-xr-xgit-svnimport.perl25
-rwxr-xr-xgit-verify-tag.sh21
-rw-r--r--git.c79
-rw-r--r--pack-objects.c2
-rw-r--r--pager.c48
-rw-r--r--read-tree.c8
-rw-r--r--refs.c9
-rw-r--r--rev-list.c737
-rw-r--r--rev-parse.c2
-rw-r--r--revision.c722
-rw-r--r--revision.h57
-rw-r--r--show-branch.c13
-rwxr-xr-xt/t3600-rm.sh23
-rwxr-xr-xt/t6001-rev-list-merge-order.sh462
-rwxr-xr-xt/t7001-mv.sh18
-rwxr-xr-xt/t8001-annotate.sh90
-rw-r--r--tar-tree.c10
-rw-r--r--update-index.c4
54 files changed, 2905 insertions, 2456 deletions
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 02cabc935e..910457d3b3 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -9,7 +9,8 @@ git-am - Apply a series of patches in a mailbox
SYNOPSIS
--------
[verse]
-'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>...
+'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+ [--interactive] [--whitespace=<option>] <mbox>...
'git-am' [--skip | --resolved]
DESCRIPTION
@@ -46,6 +47,10 @@ OPTIONS
Skip the current patch. This is only meaningful when
restarting an aborted patch.
+--whitespace=<option>::
+ This flag is passed to the `git-apply` program that applies
+ the patch.
+
--interactive::
Run interactively, just like git-applymbox.
@@ -80,7 +85,7 @@ names.
SEE ALSO
--------
-gitlink:git-applymbox[1], gitlink:git-applypatch[1].
+gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1].
Author
diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt
index 75076b6121..1c64a1aa82 100644
--- a/Documentation/git-apply.txt
+++ b/Documentation/git-apply.txt
@@ -11,6 +11,7 @@ SYNOPSIS
[verse]
'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply]
[--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM]
+ [--whitespace=<nowarn|warn|error|error-all|strip>]
[<patch>...]
DESCRIPTION
@@ -97,6 +98,35 @@ OPTIONS
result. This allows binary files to be patched in a
very limited way.
+--whitespace=<option>::
+ When applying a patch, detect a new or modified line
+ that ends with trailing whitespaces (this includes a
+ line that solely consists of whitespaces). By default,
+ the command outputs warning messages and applies the
+ patch.
+ When `git-apply` is used for statistics and not applying a
+ patch, it defaults to `nowarn`.
+ You can use different `<option>` to control this
+ behaviour:
++
+* `nowarn` turns off the trailing whitespace warning.
+* `warn` outputs warnings for a few such errors, but applies the
+ patch (default).
+* `error` outputs warnings for a few such errors, and refuses
+ to apply the patch.
+* `error-all` is similar to `error` but shows all errors.
+* `strip` outputs warnings for a few such errors, strips out the
+ trailing whitespaces and applies the patch.
+
+
+Configuration
+-------------
+
+apply.whitespace::
+ When no `--whitespace` flag is given from the command
+ line, this configuration item is used as the default.
+
+
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt
index 88f07ff15d..19c9c51cff 100644
--- a/Documentation/git-cvsserver.txt
+++ b/Documentation/git-cvsserver.txt
@@ -54,6 +54,30 @@ INSTALLATION
of branches in git).
$ cvs co -d mylocaldir master
+Eclipse CVS Client Notes
+------------------------
+
+To get a checkout with the Eclipse CVS client:
+
+1. Create a new project from CVS checkout, giving it repository and module
+2. Context Menu->Team->Share Project...
+3. Enter the repository and module information again and click Finish
+4. The Synchronize view appears. Untick "launch commit wizard" to avoid
+committing the .project file, and select HEAD as the tag to synchronize to.
+Update all incoming changes.
+
+Note that most versions of Eclipse ignore CVS_SERVER (which you can set in
+the Preferences->Team->CVS->ExtConnection pane), so you may have to
+rename, alias or symlink git-cvsserver to 'cvs' on the server.
+
+Clients known to work
+---------------------
+
+CVS 1.12.9 on Debian
+CVS 1.11.17 on MacOSX (from Fink package)
+Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes)
+TortoiseCVS
+
Operations supported
--------------------
diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt
index 6fbd6d9368..844cfda8d2 100644
--- a/Documentation/git-read-tree.txt
+++ b/Documentation/git-read-tree.txt
@@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index
SYNOPSIS
--------
-'git-read-tree' (<tree-ish> | [[-m | --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
+'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]])
DESCRIPTION
@@ -50,6 +50,19 @@ OPTIONS
trees that are not directly related to the current
working tree status into a temporary index file.
+--aggressive::
+ Usually a three-way merge by `git-read-tree` resolves
+ the merge for really trivial cases and leaves other
+ cases unresolved in the index, so that Porcelains can
+ implement different merge policies. This flag makes the
+ command to resolve a few more cases internally:
++
+* when one side removes a path and the other side leaves the path
+ unmodified. The resolution is to remove that path.
+* when both sides remove a path. The resolution is to remove that path.
+* when both sides adds a path identically. The resolution
+ is to add that path.
+
<tree-ish#>::
The id of the tree object(s) to be read/merged.
diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt
index 33fcde452a..00efde5f0f 100644
--- a/Documentation/git-repo-config.txt
+++ b/Documentation/git-repo-config.txt
@@ -8,6 +8,7 @@ git-repo-config - Get and set options in .git/config.
SYNOPSIS
--------
+[verse]
'git-repo-config' [type] name [value [value_regex]]
'git-repo-config' [type] --replace-all name [value [value_regex]]
'git-repo-config' [type] --get name [value_regex]
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 1c6146c764..8255ae1bce 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -16,9 +16,9 @@ SYNOPSIS
[ \--no-merges ]
[ \--remove-empty ]
[ \--all ]
- [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ]
+ [ \--topo-order ]
[ \--parents ]
- [ \--objects [ \--unpacked ] ]
+ [ [\--objects | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ]
[ \--bisect ]
<commit>... [ \-- <paths>... ]
@@ -53,6 +53,14 @@ OPTIONS
which I need to download if I have the commit object 'bar', but
not 'foo'".
+--objects-edge::
+ Similar to `--objects`, but also print the IDs of
+ excluded commits refixed with a `-` character. This is
+ used by `git-pack-objects` to build 'thin' pack, which
+ records objects in deltified form based on objects
+ contained in these excluded commits to reduce network
+ traffic.
+
--unpacked::
Only useful with `--objects`; print the object IDs that
are not in packs.
@@ -94,57 +102,10 @@ OPTIONS
topological order (i.e. descendant commits are shown
before their parents).
---merge-order::
- When specified the commit history is decomposed into a unique
- sequence of minimal, non-linear epochs and maximal, linear epochs.
- Non-linear epochs are then linearised by sorting them into merge
- order, which is described below.
-+
-Maximal, linear epochs correspond to periods of sequential development.
-Minimal, non-linear epochs correspond to periods of divergent development
-followed by a converging merge. The theory of epochs is described in more
-detail at
-link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/].
-+
-The merge order for a non-linear epoch is defined as a linearisation for which
-the following invariants are true:
-+
- 1. if a commit P is reachable from commit N, commit P sorts after commit N
- in the linearised list.
- 2. if Pi and Pj are any two parents of a merge M (with i < j), then any
- commit N, such that N is reachable from Pj but not reachable from Pi,
- sorts before all commits reachable from Pi.
-+
-Invariant 1 states that later commits appear before earlier commits they are
-derived from.
-+
-Invariant 2 states that commits unique to "later" parents in a merge, appear
-before all commits from "earlier" parents of a merge.
-
---show-breaks::
- Each item of the list is output with a 2-character prefix consisting
- of one of: (|), (^), (=) followed by a space.
-+
-Commits marked with (=) represent the boundaries of minimal, non-linear epochs
-and correspond either to the start of a period of divergent development or to
-the end of such a period.
-+
-Commits marked with (|) are direct parents of commits immediately preceding
-the marked commit in the list.
-+
-Commits marked with (^) are not parents of the immediately preceding commit.
-These "breaks" represent necessary discontinuities implied by trying to
-represent an arbitrary DAG in a linear form.
-+
-`--show-breaks` is only valid if `--merge-order` is also specified.
-
-
Author
------
Written by Linus Torvalds <torvalds@osdl.org>
-Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com>
-
Documentation
--------------
Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt
index 912a80865e..9d3865719c 100644
--- a/Documentation/git-svnimport.txt
+++ b/Documentation/git-svnimport.txt
@@ -9,6 +9,7 @@ git-svnimport - Import a SVN repository into git
SYNOPSIS
--------
+[verse]
'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ]
[ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev]
[ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ]
@@ -82,6 +83,11 @@ When importing incrementally, you might need to edit the .git/svn2git file.
"username". If encountering a commit made by a user not in the
list, abort.
+ For convenience, this data is saved to $GIT_DIR/svn-authors
+ each time the -A option is provided, and read from that same
+ file each time git-svnimport is run with an existing GIT
+ repository without -A.
+
-m::
Attempt to detect merges based on the commit message. This option
will enable default regexes that try to capture the name source
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
new file mode 100644
index 0000000000..00e57a69ae
--- /dev/null
+++ b/Documentation/git-tools.txt
@@ -0,0 +1,97 @@
+A short git tools survey
+========================
+
+
+Introduction
+------------
+
+Apart from git contrib/ area there are some others third-party tools
+you may want to look.
+
+This document presents a brief summary of each tool and the corresponding
+link.
+
+
+Alternative/Augmentative Procelains
+-----------------------------------
+
+ - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/)
+
+ Cogito is a version control system layered on top of the git tree history
+ storage system. It aims at seamless user interface and ease of use,
+ providing generally smoother user experience than the "raw" Core GIT
+ itself and indeed many other version control systems.
+
+
+ - *pg* (http://www.spearce.org/category/projects/scm/pg/)
+
+ pg is a shell script wrapper around GIT to help the user manage a set of
+ patches to files. pg is somewhat like quilt or StGIT, but it does have a
+ slightly different feature set.
+
+
+ - *StGit* (http://www.procode.org/stgit/)
+
+ Stacked GIT provides a quilt-like patch management functionality in the
+ GIT environment. You can easily manage your patches in the scope of GIT
+ until they get merged upstream.
+
+
+History Viewers
+---------------
+
+ - *gitk* (shipped with git-core)
+
+ gitk is a simple TK GUI for browsing history of GIT repositories easily.
+
+
+ - *gitview* (contrib/)
+
+ gitview is a GTK based repository browser for git
+
+
+ - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/)
+
+ GITweb provides full-fledged web interface for GIT repositories.
+
+
+ - *qgit* (http://digilander.libero.it/mcostalba/)
+
+ QGit is a git/StGIT GUI viewer built on Qt/C++. QGit could be used
+ to browse history and directory tree, view annotated files, commit
+ changes cherry picking single files or applying patches.
+ Currently it is the fastest and most feature rich among the git
+ viewers and commit tools.
+
+
+
+Foreign SCM interface
+---------------------
+
+ - *git-svn* (contrib/)
+
+ git-svn is a simple conduit for changesets between a single Subversion
+ branch and git.
+
+
+ - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc)
+
+ These utilities convert patch series in a quilt repository and commit
+ series in git back and forth.
+
+
+Others
+------
+
+ - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/)
+
+ Commit Tool or (h)gct is a GUI enabled commit tool for git and
+ Mercurial (hg). It allows the user to view diffs, select which files
+ to committed (or ignored / reverted) write commit messages and
+ perform the commit itself.
+
+ - *git.el* (contrib/)
+
+ This is an Emacs interface for git. The user interface is modeled on
+ pcl-cvs. It has been developed on Emacs 21 and will probably need some
+ tweaking to work on XEmacs.
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 1056b7c817..d6d1ae0338 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -7,8 +7,11 @@ DEF_VER=v1.2.GIT
# (included in release tarballs), then default
if VN=$(git-describe --abbrev=4 HEAD 2>/dev/null); then
VN=$(echo "$VN" | sed -e 's/-/./g');
-else
+elif test -f version
+then
VN=$(cat version) || VN="$DEF_VER"
+else
+ VN="$DEF_VER"
fi
VN=$(expr "$VN" : v*'\(.*\)')
diff --git a/INSTALL b/INSTALL
index 433449fd8e..63af8eccf3 100644
--- a/INSTALL
+++ b/INSTALL
@@ -40,9 +40,7 @@ Issues of note:
If you don't have openssl, you can use one of the SHA1 libraries
that come with git (git includes the one from Mozilla, and has
- its own PowerPC-optimized one too - see the Makefile), and you
- can avoid the bignum support by excising git-rev-list support
- for "--merge-order" (by hand).
+ its own PowerPC and ARM optimized ones too - see the Makefile).
- "libcurl" and "curl" executable. git-http-fetch and
git-fetch use them. If you do not use http
diff --git a/Makefile b/Makefile
index a5eb0c4beb..ab2890d638 100644
--- a/Makefile
+++ b/Makefile
@@ -6,8 +6,8 @@ all:
# 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. You will
-# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1.
+# Define NO_OPENSSL environment variable if you do not have OpenSSL.
+# This also implies MOZILLA_SHA1.
#
# Define NO_CURL if you do not have curl installed. git-http-pull and
# git-http-push are not built, and you cannot use http:// and https://
@@ -165,7 +165,7 @@ PROGRAMS = \
git-upload-pack$X git-verify-pack$X git-write-tree$X \
git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \
git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \
- git-describe$X git-merge-tree$X
+ git-describe$X git-merge-tree$X git-blame$X
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS)
@@ -190,9 +190,9 @@ PYMODULES = \
LIB_FILE=libgit.a
LIB_H = \
- blob.h cache.h commit.h count-delta.h csum-file.h delta.h \
- diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \
- run-command.h strbuf.h tag.h tree.h git-compat-util.h
+ blob.h cache.h commit.h csum-file.h delta.h \
+ diff.h object.h pack.h pkt-line.h quote.h refs.h \
+ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h
DIFF_OBJS = \
diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \
@@ -200,13 +200,13 @@ DIFF_OBJS = \
diffcore-delta.o
LIB_OBJS = \
- blob.o commit.o connect.o count-delta.o csum-file.o \
+ blob.o commit.o connect.o csum-file.o \
date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \
object.o pack-check.o patch-delta.o path.o pkt-line.o \
quote.o read-cache.o refs.o run-command.o \
server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \
tag.o tree.o usage.o config.o environment.o ctype.o copy.o \
- fetch-clone.o \
+ fetch-clone.o revision.o pager.o \
$(DIFF_OBJS)
LIBS = $(LIB_FILE)
@@ -329,7 +329,6 @@ ifndef NO_CURL
endif
ifndef NO_OPENSSL
- LIB_OBJS += epoch.o
OPENSSL_LIBSSL = -lssl
ifdef OPENSSLDIR
# Again this may be problematic -- gcc does not always want -R.
@@ -455,7 +454,7 @@ strip: $(PROGRAMS) git$X
git$X: git.c $(LIB_FILE)
$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
- $(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE)
+ $(ALL_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) $(LIBS)
$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
rm -f $@
diff --git a/apply.c b/apply.c
index 244718ca13..c369966867 100644
--- a/apply.c
+++ b/apply.c
@@ -32,7 +32,57 @@ static int no_add = 0;
static int show_index_info = 0;
static int line_termination = '\n';
static const char apply_usage[] =
-"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>...";
+"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>...";
+
+static enum whitespace_eol {
+ nowarn_whitespace,
+ warn_on_whitespace,
+ error_on_whitespace,
+ strip_whitespace,
+} new_whitespace = warn_on_whitespace;
+static int whitespace_error = 0;
+static int squelch_whitespace_errors = 5;
+static int applied_after_stripping = 0;
+static const char *patch_input_file = NULL;
+
+static void parse_whitespace_option(const char *option)
+{
+ if (!option) {
+ new_whitespace = warn_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "warn")) {
+ new_whitespace = warn_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "nowarn")) {
+ new_whitespace = nowarn_whitespace;
+ return;
+ }
+ if (!strcmp(option, "error")) {
+ new_whitespace = error_on_whitespace;
+ return;
+ }
+ if (!strcmp(option, "error-all")) {
+ new_whitespace = error_on_whitespace;
+ squelch_whitespace_errors = 0;
+ return;
+ }
+ if (!strcmp(option, "strip")) {
+ new_whitespace = strip_whitespace;
+ return;
+ }
+ die("unrecognized whitespace option '%s'", option);
+}
+
+static void set_default_whitespace_mode(const char *whitespace_option)
+{
+ if (!whitespace_option && !apply_default_whitespace) {
+ new_whitespace = (apply
+ ? warn_on_whitespace
+ : nowarn_whitespace);
+ }
+}
/*
* For "diff-stat" like behaviour, we keep track of the biggest change
@@ -815,6 +865,25 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s
oldlines--;
break;
case '+':
+ /*
+ * We know len is at least two, since we have a '+' and
+ * we checked that the last character was a '\n' above.
+ * That is, an addition of an empty line would check
+ * the '+' here. Sneaky...
+ */
+ if ((new_whitespace != nowarn_whitespace) &&
+ isspace(line[len-2])) {
+ whitespace_error++;
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors <
+ whitespace_error)
+ ;
+ else {
+ fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n",
+ patch_input_file,
+ linenr, len-2, line+1);
+ }
+ }
added++;
newlines--;
break;
@@ -1092,6 +1161,28 @@ struct buffer_desc {
unsigned long alloc;
};
+static int apply_line(char *output, const char *patch, int plen)
+{
+ /* plen is number of bytes to be copied from patch,
+ * starting at patch+1 (patch[0] is '+'). Typically
+ * patch[plen] is '\n'.
+ */
+ int add_nl_to_tail = 0;
+ if ((new_whitespace == strip_whitespace) &&
+ 1 < plen && isspace(patch[plen-1])) {
+ if (patch[plen] == '\n')
+ add_nl_to_tail = 1;
+ plen--;
+ while (0 < plen && isspace(patch[plen]))
+ plen--;
+ applied_after_stripping++;
+ }
+ memcpy(output, patch + 1, plen);
+ if (add_nl_to_tail)
+ output[plen++] = '\n';
+ return plen;
+}
+
static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
{
char *buf = desc->buffer;
@@ -1127,10 +1218,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag)
break;
/* Fall-through for ' ' */
case '+':
- if (*patch != '+' || !no_add) {
- memcpy(new + newsize, patch + 1, plen);
- newsize += plen;
- }
+ if (*patch != '+' || !no_add)
+ newsize += apply_line(new + newsize, patch,
+ plen);
break;
case '@': case '\\':
/* Ignore it, we already handled it */
@@ -1699,7 +1789,7 @@ static int use_patch(struct patch *p)
return 1;
}
-static int apply_patch(int fd)
+static int apply_patch(int fd, const char *filename)
{
int newfd;
unsigned long offset, size;
@@ -1707,6 +1797,7 @@ static int apply_patch(int fd)
struct patch *list = NULL, **listp = &list;
int skipped_patch = 0;
+ patch_input_file = filename;
if (!buffer)
return -1;
offset = 0;
@@ -1733,6 +1824,9 @@ static int apply_patch(int fd)
}
newfd = -1;
+ if (whitespace_error && (new_whitespace == error_on_whitespace))
+ apply = 0;
+
write_index = check_index && apply;
if (write_index)
newfd = hold_index_file_for_update(&cache_file, get_index_file());
@@ -1769,17 +1863,28 @@ static int apply_patch(int fd)
return 0;
}
+static int git_apply_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "apply.whitespace")) {
+ apply_default_whitespace = strdup(value);
+ return 0;
+ }
+ return git_default_config(var, value);
+}
+
+
int main(int argc, char **argv)
{
int i;
int read_stdin = 1;
+ const char *whitespace_option = NULL;
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
int fd;
if (!strcmp(arg, "-")) {
- apply_patch(0);
+ apply_patch(0, "<stdin>");
read_stdin = 0;
continue;
}
@@ -1839,11 +1944,18 @@ int main(int argc, char **argv)
line_termination = 0;
continue;
}
+ if (!strncmp(arg, "--whitespace=", 13)) {
+ whitespace_option = arg + 13;
+ parse_whitespace_option(arg + 13);
+ continue;
+ }
if (check_index && prefix_length < 0) {
prefix = setup_git_directory();
prefix_length = prefix ? strlen(prefix) : 0;
- git_config(git_default_config);
+ git_config(git_apply_config);
+ if (!whitespace_option && apply_default_whitespace)
+ parse_whitespace_option(apply_default_whitespace);
}
if (0 < prefix_length)
arg = prefix_filename(prefix, prefix_length, arg);
@@ -1852,10 +1964,38 @@ int main(int argc, char **argv)
if (fd < 0)
usage(apply_usage);
read_stdin = 0;
- apply_patch(fd);
+ set_default_whitespace_mode(whitespace_option);
+ apply_patch(fd, arg);
close(fd);
}
+ set_default_whitespace_mode(whitespace_option);
if (read_stdin)
- apply_patch(0);
+ apply_patch(0, "<stdin>");
+ if (whitespace_error) {
+ if (squelch_whitespace_errors &&
+ squelch_whitespace_errors < whitespace_error) {
+ int squelched =
+ whitespace_error - squelch_whitespace_errors;
+ fprintf(stderr, "warning: squelched %d whitespace error%s\n",
+ squelched,
+ squelched == 1 ? "" : "s");
+ }
+ if (new_whitespace == error_on_whitespace)
+ die("%d line%s add%s trailing whitespaces.",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ if (applied_after_stripping)
+ fprintf(stderr, "warning: %d line%s applied after"
+ " stripping trailing whitespaces.\n",
+ applied_after_stripping,
+ applied_after_stripping == 1 ? "" : "s");
+ else if (whitespace_error)
+ fprintf(stderr, "warning: %d line%s add%s trailing"
+ " whitespaces.\n",
+ whitespace_error,
+ whitespace_error == 1 ? "" : "s",
+ whitespace_error == 1 ? "s" : "");
+ }
return 0;
}
diff --git a/blame.c b/blame.c
new file mode 100644
index 0000000000..7308c36d23
--- /dev/null
+++ b/blame.c
@@ -0,0 +1,562 @@
+/*
+ * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se>
+ */
+
+#include <assert.h>
+#include <time.h>
+#include <sys/time.h>
+
+#include "cache.h"
+#include "refs.h"
+#include "tag.h"
+#include "commit.h"
+#include "tree.h"
+#include "blob.h"
+#include "diff.h"
+#include "revision.h"
+
+#define DEBUG 0
+
+struct commit **blame_lines;
+int num_blame_lines;
+
+struct util_info {
+ int *line_map;
+ unsigned char sha1[20]; /* blob sha, not commit! */
+ char *buf;
+ unsigned long size;
+ int num_lines;
+// const char* path;
+};
+
+struct chunk {
+ int off1, len1; // ---
+ int off2, len2; // +++
+};
+
+struct patch {
+ struct chunk *chunks;
+ int num;
+};
+
+static void get_blob(struct commit *commit);
+
+/* Only used for statistics */
+static int num_get_patch = 0;
+static int num_commits = 0;
+static int patch_time = 0;
+
+#define TEMPFILE_PATH_LEN 60
+static struct patch *get_patch(struct commit *commit, struct commit *other)
+{
+ struct patch *ret;
+ struct util_info *info_c = (struct util_info *)commit->object.util;
+ struct util_info *info_o = (struct util_info *)other->object.util;
+ char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN];
+ char diff_cmd[TEMPFILE_PATH_LEN*2 + 20];
+ struct timeval tv_start, tv_end;
+ int fd;
+ FILE *fin;
+ char buf[1024];
+
+ ret = xmalloc(sizeof(struct patch));
+ ret->chunks = NULL;
+ ret->num = 0;
+
+ get_blob(commit);
+ get_blob(other);
+
+ gettimeofday(&tv_start, NULL);
+
+ fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+ if (fd < 0)
+ die("unable to create temp-file: %s", strerror(errno));
+
+ if (xwrite(fd, info_c->buf, info_c->size) != info_c->size)
+ die("write failed: %s", strerror(errno));
+ close(fd);
+
+ fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX");
+ if (fd < 0)
+ die("unable to create temp-file: %s", strerror(errno));
+
+ if (xwrite(fd, info_o->buf, info_o->size) != info_o->size)
+ die("write failed: %s", strerror(errno));
+ close(fd);
+
+ sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2);
+ fin = popen(diff_cmd, "r");
+ if (!fin)
+ die("popen failed: %s", strerror(errno));
+
+ while (fgets(buf, sizeof(buf), fin)) {
+ struct chunk *chunk;
+ char *start, *sp;
+
+ if (buf[0] != '@' || buf[1] != '@')
+ continue;
+
+ if (DEBUG)
+ printf("chunk line: %s", buf);
+ ret->num++;
+ ret->chunks = xrealloc(ret->chunks,
+ sizeof(struct chunk) * ret->num);
+ chunk = &ret->chunks[ret->num - 1];
+
+ assert(!strncmp(buf, "@@ -", 4));
+
+ start = buf + 4;
+ sp = index(start, ' ');
+ *sp = '\0';
+ if (index(start, ',')) {
+ int ret =
+ sscanf(start, "%d,%d", &chunk->off1, &chunk->len1);
+ assert(ret == 2);
+ } else {
+ int ret = sscanf(start, "%d", &chunk->off1);
+ assert(ret == 1);
+ chunk->len1 = 1;
+ }
+ *sp = ' ';
+
+ start = sp + 1;
+ sp = index(start, ' ');
+ *sp = '\0';
+ if (index(start, ',')) {
+ int ret =
+ sscanf(start, "%d,%d", &chunk->off2, &chunk->len2);
+ assert(ret == 2);
+ } else {
+ int ret = sscanf(start, "%d", &chunk->off2);
+ assert(ret == 1);
+ chunk->len2 = 1;
+ }
+ *sp = ' ';
+
+ if (chunk->len1 == 0)
+ chunk->off1++;
+ if (chunk->len2 == 0)
+ chunk->off2++;
+
+ if (chunk->off1 > 0)
+ chunk->off1--;
+ if (chunk->off2 > 0)
+ chunk->off2--;
+
+ assert(chunk->off1 >= 0);
+ assert(chunk->off2 >= 0);
+ }
+ pclose(fin);
+ unlink(tmp_path1);
+ unlink(tmp_path2);
+
+ gettimeofday(&tv_end, NULL);
+ patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) +
+ tv_end.tv_usec - tv_start.tv_usec;
+
+ num_get_patch++;
+ return ret;
+}
+
+static void free_patch(struct patch *p)
+{
+ free(p->chunks);
+ free(p);
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+ int baselen, const char *pathname,
+ unsigned mode, int stage);
+
+static unsigned char blob_sha1[20];
+static int get_blob_sha1(struct tree *t, const char *pathname,
+ unsigned char *sha1)
+{
+ int i;
+ const char *pathspec[2];
+ pathspec[0] = pathname;
+ pathspec[1] = NULL;
+ memset(blob_sha1, 0, sizeof(blob_sha1));
+ read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal);
+
+ for (i = 0; i < 20; i++) {
+ if (blob_sha1[i] != 0)
+ break;
+ }
+
+ if (i == 20)
+ return -1;
+
+ memcpy(sha1, blob_sha1, 20);
+ return 0;
+}
+
+static int get_blob_sha1_internal(unsigned char *sha1, const char *base,
+ int baselen, const char *pathname,
+ unsigned mode, int stage)
+{
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ memcpy(blob_sha1, sha1, 20);
+ return -1;
+}
+
+static void get_blob(struct commit *commit)
+{
+ struct util_info *info = commit->object.util;
+ char type[20];
+
+ if (info->buf)
+ return;
+
+ info->buf = read_sha1_file(info->sha1, type, &info->size);
+
+ assert(!strcmp(type, "blob"));
+}
+
+/* For debugging only */
+static void print_patch(struct patch *p)
+{
+ int i;
+ printf("Num chunks: %d\n", p->num);
+ for (i = 0; i < p->num; i++) {
+ printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1,
+ p->chunks[i].off2, p->chunks[i].len2);
+ }
+}
+
+/* For debugging only */
+static void print_map(struct commit *cmit, struct commit *other)
+{
+ struct util_info *util = cmit->object.util;
+ struct util_info *util2 = other->object.util;
+
+ int i;
+ int max =
+ util->num_lines >
+ util2->num_lines ? util->num_lines : util2->num_lines;
+ int num;
+
+ for (i = 0; i < max; i++) {
+ printf("i: %d ", i);
+ num = -1;
+
+ if (i < util->num_lines) {
+ num = util->line_map[i];
+ printf("%d\t", num);
+ } else
+ printf("\t");
+
+ if (i < util2->num_lines) {
+ int num2 = util2->line_map[i];
+ printf("%d\t", num2);
+ if (num != -1 && num2 != num)
+ printf("---");
+ } else
+ printf("\t");
+
+ printf("\n");
+ }
+}
+
+// p is a patch from commit to other.
+static void fill_line_map(struct commit *commit, struct commit *other,
+ struct patch *p)
+{
+ struct util_info *util = commit->object.util;
+ struct util_info *util2 = other->object.util;
+ int *map = util->line_map;
+ int *map2 = util2->line_map;
+ int cur_chunk = 0;
+ int i1, i2;
+
+ if (p->num && DEBUG)
+ print_patch(p);
+
+ if (DEBUG)
+ printf("num lines 1: %d num lines 2: %d\n", util->num_lines,
+ util2->num_lines);
+
+ for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) {
+ struct chunk *chunk = NULL;
+ if (cur_chunk < p->num)
+ chunk = &p->chunks[cur_chunk];
+
+ if (chunk && chunk->off1 == i1) {
+ if (DEBUG && i2 != chunk->off2)
+ printf("i2: %d off2: %d\n", i2, chunk->off2);
+
+ assert(i2 == chunk->off2);
+
+ i1--;
+ i2--;
+ if (chunk->len1 > 0)
+ i1 += chunk->len1;
+
+ if (chunk->len2 > 0)
+ i2 += chunk->len2;
+
+ cur_chunk++;
+ } else {
+ if (i2 >= util2->num_lines)
+ break;
+
+ if (map[i1] != map2[i2] && map[i1] != -1) {
+ if (DEBUG)
+ printf("map: i1: %d %d %p i2: %d %d %p\n",
+ i1, map[i1],
+ i1 != -1 ? blame_lines[map[i1]] : NULL,
+ i2, map2[i2],
+ i2 != -1 ? blame_lines[map2[i2]] : NULL);
+ if (map2[i2] != -1 &&
+ blame_lines[map[i1]] &&
+ !blame_lines[map2[i2]])
+ map[i1] = map2[i2];
+ }
+
+ if (map[i1] == -1 && map2[i2] != -1)
+ map[i1] = map2[i2];
+ }
+
+ if (DEBUG > 1)
+ printf("l1: %d l2: %d i1: %d i2: %d\n",
+ map[i1], map2[i2], i1, i2);
+ }
+}
+
+static int map_line(struct commit *commit, int line)
+{
+ struct util_info *info = commit->object.util;
+ assert(line >= 0 && line < info->num_lines);
+ return info->line_map[line];
+}
+
+static int fill_util_info(struct commit *commit, const char *path)
+{
+ struct util_info *util;
+ if (commit->object.util)
+ return 0;
+
+ util = xmalloc(sizeof(struct util_info));
+
+ if (get_blob_sha1(commit->tree, path, util->sha1)) {
+ free(util);
+ return 1;
+ } else {
+ util->buf = NULL;
+ util->size = 0;
+ util->line_map = NULL;
+ util->num_lines = -1;
+ commit->object.util = util;
+ return 0;
+ }
+}
+
+static void alloc_line_map(struct commit *commit)
+{
+ struct util_info *util = commit->object.util;
+ int i;
+
+ if (util->line_map)
+ return;
+
+ get_blob(commit);
+
+ util->num_lines = 0;
+ for (i = 0; i < util->size; i++) {
+ if (util->buf[i] == '\n')
+ util->num_lines++;
+ }
+ if(util->buf[util->size - 1] != '\n')
+ util->num_lines++;
+
+ util->line_map = xmalloc(sizeof(int) * util->num_lines);
+
+ for (i = 0; i < util->num_lines; i++)
+ util->line_map[i] = -1;
+}
+
+static void init_first_commit(struct commit* commit, const char* filename)
+{
+ struct util_info* util;
+ int i;
+
+ if (fill_util_info(commit, filename))
+ die("fill_util_info failed");
+
+ alloc_line_map(commit);
+
+ util = commit->object.util;
+ num_blame_lines = util->num_lines;
+
+ for (i = 0; i < num_blame_lines; i++)
+ util->line_map[i] = i;
+}
+
+
+static void process_commits(struct rev_info *rev, const char *path,
+ struct commit** initial)
+{
+ int i;
+ struct util_info* util;
+ int lines_left;
+ int *blame_p;
+ int *new_lines;
+ int new_lines_len;
+
+ struct commit* commit = get_revision(rev);
+ assert(commit);
+ init_first_commit(commit, path);
+
+ util = commit->object.util;
+ num_blame_lines = util->num_lines;
+ blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines);
+ for (i = 0; i < num_blame_lines; i++)
+ blame_lines[i] = NULL;
+
+ lines_left = num_blame_lines;
+ blame_p = xmalloc(sizeof(int) * num_blame_lines);
+ new_lines = xmalloc(sizeof(int) * num_blame_lines);
+ do {
+ struct commit_list *parents;
+ int num_parents;
+ struct util_info *util;
+
+ if (DEBUG)
+ printf("\nProcessing commit: %d %s\n", num_commits,
+ sha1_to_hex(commit->object.sha1));
+
+ if (lines_left == 0)
+ return;
+
+ num_commits++;
+ memset(blame_p, 0, sizeof(int) * num_blame_lines);
+ new_lines_len = 0;
+ num_parents = 0;
+ for (parents = commit->parents;
+ parents != NULL; parents = parents->next)
+ num_parents++;
+
+ if(num_parents == 0)
+ *initial = commit;
+
+ if(fill_util_info(commit, path))
+ continue;
+
+ alloc_line_map(commit);
+ util = commit->object.util;
+
+ for (parents = commit->parents;
+ parents != NULL; parents = parents->next) {
+ struct commit *parent = parents->item;
+ struct patch *patch;
+
+ if (parse_commit(parent) < 0)
+ die("parse_commit error");
+
+ if (DEBUG)
+ printf("parent: %s\n",
+ sha1_to_hex(parent->object.sha1));
+
+ if(fill_util_info(parent, path)) {
+ num_parents--;
+ continue;
+ }
+
+ patch = get_patch(parent, commit);
+ alloc_line_map(parent);
+ fill_line_map(parent, commit, patch);
+
+ for (i = 0; i < patch->num; i++) {
+ int l;
+ for (l = 0; l < patch->chunks[i].len2; l++) {
+ int mapped_line =
+ map_line(commit, patch->chunks[i].off2 + l);
+ if (mapped_line != -1) {
+ blame_p[mapped_line]++;
+ if (blame_p[mapped_line] == num_parents)
+ new_lines[new_lines_len++] = mapped_line;
+ }
+ }
+ }
+ free_patch(patch);
+ }
+
+ if (DEBUG)
+ printf("parents: %d\n", num_parents);
+
+ for (i = 0; i < new_lines_len; i++) {
+ int mapped_line = new_lines[i];
+ if (blame_lines[mapped_line] == NULL) {
+ blame_lines[mapped_line] = commit;
+ lines_left--;
+ if (DEBUG)
+ printf("blame: mapped: %d i: %d\n",
+ mapped_line, i);
+ }
+ }
+ } while ((commit = get_revision(rev)) != NULL);
+}
+
+int main(int argc, const char **argv)
+{
+ int i;
+ struct commit *initial = NULL;
+ unsigned char sha1[20];
+ const char* filename;
+ int num_args;
+ const char* args[10];
+ struct rev_info rev;
+
+ setup_git_directory();
+
+ if (argc != 3)
+ die("Usage: blame commit-ish file");
+
+
+ filename = argv[2];
+
+ {
+ struct commit* commit;
+ if (get_sha1(argv[1], sha1))
+ die("get_sha1 failed");
+ commit = lookup_commit_reference(sha1);
+
+ if (fill_util_info(commit, filename)) {
+ printf("%s not found in %s\n", filename, argv[1]);
+ return 1;
+ }
+ }
+
+ num_args = 0;
+ args[num_args++] = NULL;
+ args[num_args++] = "--topo-order";
+ args[num_args++] = "--remove-empty";
+ args[num_args++] = argv[1];
+ args[num_args++] = "--";
+ args[num_args++] = filename;
+ args[num_args] = NULL;
+
+ setup_revisions(num_args, args, &rev, "HEAD");
+ prepare_revision_walk(&rev);
+ process_commits(&rev, filename, &initial);
+
+ for (i = 0; i < num_blame_lines; i++) {
+ struct commit *c = blame_lines[i];
+ if (!c)
+ c = initial;
+
+ printf("%d %.8s\n", i, sha1_to_hex(c->object.sha1));
+// printf("%d %s\n", i, find_unique_abbrev(blame_lines[i]->object.sha1, 6));
+ }
+
+ if (DEBUG) {
+ printf("num get patch: %d\n", num_get_patch);
+ printf("num commits: %d\n", num_commits);
+ printf("patch time: %f\n", patch_time / 1000000.0);
+ printf("initial: %s\n", sha1_to_hex(initial->object.sha1));
+ }
+
+ return 0;
+}
diff --git a/cache.h b/cache.h
index 58eec00e0e..8dc1de16e4 100644
--- a/cache.h
+++ b/cache.h
@@ -161,11 +161,13 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path);
extern int commit_index_file(struct cache_file *);
extern void rollback_index_file(struct cache_file *);
+/* Environment bits from configuration mechanism */
extern int trust_executable_bit;
extern int assume_unchanged;
extern int only_use_symrefs;
extern int diff_rename_limit_default;
extern int shared_repository;
+extern const char *apply_default_whitespace;
#define GIT_REPO_VERSION 0
extern int repository_format_version;
@@ -352,4 +354,7 @@ extern int copy_fd(int ifd, int ofd);
extern int receive_unpack_pack(int fd[2], const char *me, int quiet);
extern int receive_keep_pack(int fd[2], const char *me, int quiet);
+/* pager.c */
+extern void setup_pager(void);
+
#endif /* CACHE_H */
diff --git a/cat-file.c b/cat-file.c
index 96d66b4304..1a613f3ee5 100644
--- a/cat-file.c
+++ b/cat-file.c
@@ -4,6 +4,92 @@
* Copyright (C) Linus Torvalds, 2005
*/
#include "cache.h"
+#include "exec_cmd.h"
+
+static void flush_buffer(const char *buf, unsigned long size)
+{
+ while (size > 0) {
+ long ret = xwrite(1, buf, size);
+ if (ret < 0) {
+ /* Ignore epipe */
+ if (errno == EPIPE)
+ break;
+ die("git-cat-file: %s", strerror(errno));
+ } else if (!ret) {
+ die("git-cat-file: disk full?");
+ }
+ size -= ret;
+ buf += ret;
+ }
+}
+
+static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size)
+{
+ /* the parser in tag.c is useless here. */
+ const char *endp = buf + size;
+ const char *cp = buf;
+
+ while (cp < endp) {
+ char c = *cp++;
+ if (c != '\n')
+ continue;
+ if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) {
+ const char *tagger = cp;
+
+ /* Found the tagger line. Copy out the contents
+ * of the buffer so far.
+ */
+ flush_buffer(buf, cp - buf);
+
+ /*
+ * Do something intelligent, like pretty-printing
+ * the date.
+ */
+ while (cp < endp) {
+ if (*cp++ == '\n') {
+ /* tagger to cp is a line
+ * that has ident and time.
+ */
+ const char *sp = tagger;
+ char *ep;
+ unsigned long date;
+ long tz;
+ while (sp < cp && *sp != '>')
+ sp++;
+ if (sp == cp) {
+ /* give up */
+ flush_buffer(tagger,
+ cp - tagger);
+ break;
+ }
+ while (sp < cp &&
+ !('0' <= *sp && *sp <= '9'))
+ sp++;
+ flush_buffer(tagger, sp - tagger);
+ date = strtoul(sp, &ep, 10);
+ tz = strtol(ep, NULL, 10);
+ sp = show_date(date, tz);
+ flush_buffer(sp, strlen(sp));
+ xwrite(1, "\n", 1);
+ break;
+ }
+ }
+ break;
+ }
+ if (cp < endp && *cp == '\n')
+ /* end of header */
+ break;
+ }
+ /* At this point, we have copied out the header up to the end of
+ * the tagger line and cp points at one past \n. It could be the
+ * next header line after the tagger line, or it could be another
+ * \n that marks the end of the headers. We need to copy out the
+ * remainder as is.
+ */
+ if (cp < endp)
+ flush_buffer(cp, endp - cp);
+ return 0;
+}
int main(int argc, char **argv)
{
@@ -15,7 +101,7 @@ int main(int argc, char **argv)
setup_git_directory();
if (argc != 3 || get_sha1(argv[2], sha1))
- usage("git-cat-file [-t|-s|-e|<type>] <sha1>");
+ usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>");
opt = 0;
if ( argv[1][0] == '-' ) {
@@ -43,6 +129,23 @@ int main(int argc, char **argv)
case 'e':
return !has_sha1_file(sha1);
+ case 'p':
+ if (get_sha1(argv[2], sha1) ||
+ sha1_object_info(sha1, type, NULL))
+ die("Not a valid object name %s", argv[2]);
+
+ /* custom pretty-print here */
+ if (!strcmp(type, "tree"))
+ return execl_git_cmd("ls-tree", argv[2], NULL);
+
+ buf = read_sha1_file(sha1, type, &size);
+ if (!buf)
+ die("Cannot read object %s", argv[2]);
+ if (!strcmp(type, "tag"))
+ return pprint_tag(sha1, buf, size);
+
+ /* otherwise just spit out the data */
+ break;
case 0:
buf = read_object_with_reference(sha1, argv[1], &size, NULL);
break;
@@ -54,18 +157,6 @@ int main(int argc, char **argv)
if (!buf)
die("git-cat-file %s: bad file", argv[2]);
- while (size > 0) {
- long ret = xwrite(1, buf, size);
- if (ret < 0) {
- /* Ignore epipe */
- if (errno == EPIPE)
- break;
- die("git-cat-file: %s", strerror(errno));
- } else if (!ret) {
- die("git-cat-file: disk full?");
- }
- size -= ret;
- buf += ret;
- }
+ flush_buffer(buf, size);
return 0;
}
diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl
index 0b7416526d..3c860e458c 100755
--- a/contrib/git-svn/git-svn.perl
+++ b/contrib/git-svn/git-svn.perl
@@ -4,19 +4,12 @@
use warnings;
use strict;
use vars qw/ $AUTHOR $VERSION
- $SVN_URL $SVN_INFO $SVN_WC
+ $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID
$GIT_SVN_INDEX $GIT_SVN
$GIT_DIR $REV_DIR/;
$AUTHOR = 'Eric Wong <normalperson@yhbt.net>';
$VERSION = '0.10.0';
$GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git";
-$GIT_SVN = $ENV{GIT_SVN_ID} || 'git-svn';
-$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
-$ENV{GIT_DIR} ||= $GIT_DIR;
-$SVN_URL = undef;
-$REV_DIR = "$GIT_DIR/$GIT_SVN/revs";
-$SVN_WC = "$GIT_DIR/$GIT_SVN/tree";
-
# make sure the svn binary gives consistent output between locales and TZs:
$ENV{TZ} = 'UTC';
$ENV{LC_ALL} = 'C';
@@ -24,6 +17,7 @@ $ENV{LC_ALL} = 'C';
# If SVN:: library support is added, please make the dependencies
# optional and preserve the capability to use the command-line client.
# use eval { require SVN::... } to make it lazy load
+# We don't use any modules not in the standard Perl distribution:
use Carp qw/croak/;
use IO::File qw//;
use File::Basename qw/dirname basename/;
@@ -32,27 +26,30 @@ use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/;
use File::Spec qw//;
use POSIX qw/strftime/;
my $sha1 = qr/[a-f\d]{40}/;
-my $sha1_short = qr/[a-f\d]{6,40}/;
+my $sha1_short = qr/[a-f\d]{4,40}/;
my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit,
- $_find_copies_harder, $_l, $_version);
-
-GetOptions( 'revision|r=s' => \$_revision,
- 'no-ignore-externals' => \$_no_ignore_ext,
- 'stdin|' => \$_stdin,
- 'edit|e' => \$_edit,
- 'rmdir' => \$_rmdir,
- 'help|H|h' => \$_help,
- 'find-copies-harder' => \$_find_copies_harder,
- 'l=i' => \$_l,
- 'version|V' => \$_version,
- 'no-stop-on-copy' => \$_no_stop_copy );
+ $_find_copies_harder, $_l, $_version, $_upgrade, $_authors);
+my (@_branch_from, %tree_map, %users);
+
+my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext,
+ 'branch|b=s' => \@_branch_from,
+ 'authors-file|A=s' => \$_authors );
my %cmd = (
- fetch => [ \&fetch, "Download new revisions from SVN" ],
- init => [ \&init, "Initialize and fetch (import)"],
- commit => [ \&commit, "Commit git revisions to SVN" ],
- 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings" ],
- rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)" ],
- help => [ \&usage, "Show help" ],
+ fetch => [ \&fetch, "Download new revisions from SVN",
+ { 'revision|r=s' => \$_revision, %fc_opts } ],
+ init => [ \&init, "Initialize and fetch (import)", { } ],
+ commit => [ \&commit, "Commit git revisions to SVN",
+ { 'stdin|' => \$_stdin,
+ 'edit|e' => \$_edit,
+ 'rmdir' => \$_rmdir,
+ 'find-copies-harder' => \$_find_copies_harder,
+ 'l=i' => \$_l,
+ %fc_opts,
+ } ],
+ 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ],
+ rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)",
+ { 'no-ignore-externals' => \$_no_ignore_ext,
+ 'upgrade' => \$_upgrade } ],
);
my $cmd;
for (my $i = 0; $i < @ARGV; $i++) {
@@ -63,16 +60,23 @@ for (my $i = 0; $i < @ARGV; $i++) {
}
};
-# we may be called as git-svn-(command), or git-svn(command).
-foreach (keys %cmd) {
- if (/git\-svn\-?($_)(?:\.\w+)?$/) {
- $cmd = $1;
- last;
- }
-}
+my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd);
+
+GetOptions(%opts, 'help|H|h' => \$_help,
+ 'version|V' => \$_version,
+ 'id|i=s' => \$GIT_SVN) or exit 1;
+
+$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn';
+$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index";
+$ENV{GIT_DIR} ||= $GIT_DIR;
+$SVN_URL = undef;
+$REV_DIR = "$GIT_DIR/$GIT_SVN/revs";
+$SVN_WC = "$GIT_DIR/$GIT_SVN/tree";
+
usage(0) if $_help;
version() if $_version;
-usage(1) unless (defined $cmd);
+usage(1) unless defined $cmd;
+load_authors() if $_authors;
svn_check_ignore_externals();
$cmd{$cmd}->[0]->(@ARGV);
exit 0;
@@ -84,15 +88,25 @@ sub usage {
print $fd <<"";
git-svn - bidirectional operations between a single Subversion tree and git
Usage: $0 <command> [options] [arguments]\n
-Available commands:
+
+ print $fd "Available commands:\n" unless $cmd;
foreach (sort keys %cmd) {
- print $fd ' ',pack('A10',$_),$cmd{$_}->[1],"\n";
+ next if $cmd && $cmd ne $_;
+ print $fd ' ',pack('A13',$_),$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 ?
+ "--$_" : "-$_" }
+ split /\|/,$_)," $x\n";
+ }
}
print $fd <<"";
-\nGIT_SVN_ID may be set in the environment to an arbitrary identifier if
-you're tracking multiple SVN branches/repositories in one git repository
-and want to keep them separate.
+\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an
+arbitrary identifier if you're tracking multiple SVN branches/repositories in
+one git repository and want to keep them separate. See git-svn(1) for more
+information.
exit $exit;
}
@@ -104,15 +118,19 @@ sub version {
sub rebuild {
$SVN_URL = shift or undef;
- my $repo_uuid;
my $newest_rev = 0;
+ if ($_upgrade) {
+ sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD");
+ } else {
+ check_upgrade_needed();
+ }
my $pid = open(my $rev_list,'-|');
defined $pid or croak $!;
if ($pid == 0) {
- exec("git-rev-list","$GIT_SVN-HEAD") or croak $!;
+ exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!;
}
- my $first;
+ my $latest;
while (<$rev_list>) {
chomp;
my $c = $_;
@@ -132,18 +150,20 @@ sub rebuild {
"$c, $id\n";
}
}
+
+ # if we merged or otherwise started elsewhere, this is
+ # how we break out of it
+ next if (defined $SVN_UUID && ($uuid ne $SVN_UUID));
+ next if (defined $SVN_URL && ($url ne $SVN_URL));
+
print "r$rev = $c\n";
- unless (defined $first) {
+ unless (defined $latest) {
if (!$SVN_URL && !$url) {
croak "SVN repository location required: $url\n";
}
$SVN_URL ||= $url;
- $repo_uuid = setup_git_svn();
- $first = $rev;
- }
- if ($uuid ne $repo_uuid) {
- croak "Repository UUIDs do not match!\ngot: $uuid\n",
- "expected: $repo_uuid\n";
+ $SVN_UUID ||= setup_git_svn();
+ $latest = $rev;
}
assert_revision_eq_or_unknown($rev, $c);
sys('git-update-ref',"$GIT_SVN/revs/$rev",$c);
@@ -151,7 +171,7 @@ sub rebuild {
}
close $rev_list or croak $?;
if (!chdir $SVN_WC) {
- my @svn_co = ('svn','co',"-r$first");
+ my @svn_co = ('svn','co',"-r$latest");
push @svn_co, '--ignore-externals' unless $_no_ignore_ext;
sys(@svn_co, $SVN_URL, $SVN_WC);
chdir $SVN_WC or croak $!;
@@ -168,6 +188,13 @@ sub rebuild {
exec('git-write-tree');
}
waitpid $pid, 0;
+
+ if ($_upgrade) {
+ print STDERR <<"";
+Keeping deprecated refs/head/$GIT_SVN-HEAD for now. Please remove it
+when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN
+
+ }
}
sub init {
@@ -180,6 +207,7 @@ sub init {
sub fetch {
my (@parents) = @_;
+ check_upgrade_needed();
$SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url");
my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL);
unless ($_revision) {
@@ -199,9 +227,6 @@ sub fetch {
sys(@svn_co, $SVN_URL, $SVN_WC);
chdir $SVN_WC or croak $!;
$last_commit = git_commit($base, @parents);
- unless (-f "$GIT_DIR/refs/heads/master") {
- sys(qw(git-update-ref refs/heads/master),$last_commit);
- }
assert_svn_wc_clean($base->{revision}, $last_commit);
} else {
chdir $SVN_WC or croak $!;
@@ -217,16 +242,20 @@ sub fetch {
$last_commit = git_commit($log_msg, $last_commit, @parents);
}
assert_svn_wc_clean($last_rev, $last_commit);
+ unless (-e "$GIT_DIR/refs/heads/master") {
+ sys(qw(git-update-ref refs/heads/master),$last_commit);
+ }
return pop @$svn_log;
}
sub commit {
my (@commits) = @_;
+ check_upgrade_needed();
if ($_stdin || !@commits) {
print "Reading from stdin...\n";
@commits = ();
while (<STDIN>) {
- if (/\b([a-f\d]{6,40})\b/) {
+ if (/\b($sha1_short)\b/o) {
unshift @commits, $1;
}
}
@@ -248,7 +277,6 @@ sub commit {
chdir $SVN_WC or croak $!;
my $svn_current_rev = svn_info('.')->{'Last Changed Rev'};
foreach my $c (@revs) {
- print "Committing $c\n";
my $mods = svn_checkout_tree($svn_current_rev, $c);
if (scalar @$mods == 0) {
print "Skipping, no changes detected\n";
@@ -295,24 +323,31 @@ sub setup_git_svn {
mkpath(["$GIT_DIR/$GIT_SVN/info"]);
mkpath([$REV_DIR]);
s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url");
- my $uuid = svn_info($SVN_URL)->{'Repository UUID'} or
+ $SVN_UUID = svn_info($SVN_URL)->{'Repository UUID'} or
croak "Repository UUID unreadable\n";
- s_to_file($uuid,"$GIT_DIR/$GIT_SVN/info/uuid");
+ s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid");
open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!;
print $fd '.svn',"\n";
close $fd or croak $!;
- return $uuid;
+ return $SVN_UUID;
}
sub assert_svn_wc_clean {
my ($svn_rev, $treeish) = @_;
croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/);
croak "$treeish is not a sha1!\n" unless ($treeish =~ /^$sha1$/o);
- my $svn_info = svn_info('.');
- if ($svn_rev != $svn_info->{'Last Changed Rev'}) {
- croak "Expected r$svn_rev, got r",
- $svn_info->{'Last Changed Rev'},"\n";
+ my $lcr = svn_info('.')->{'Last Changed Rev'};
+ if ($svn_rev != $lcr) {
+ print STDERR "Checking for copy-tree ... ";
+ # use
+ my @diff = grep(/^Index: /,(safe_qx(qw(svn diff),
+ "-r$lcr:$svn_rev")));
+ if (@diff) {
+ croak "Nope! Expected r$svn_rev, got r$lcr\n";
+ } else {
+ print STDERR "OK!\n";
+ }
}
my @status = grep(!/^Performing status on external/,(`svn status`));
@status = grep(!/^\s*$/,@status);
@@ -495,7 +530,7 @@ sub svn_checkout_tree {
my ($svn_rev, $treeish) = @_;
my $from = file_to_s("$REV_DIR/$svn_rev");
assert_svn_wc_clean($svn_rev,$from);
- print "diff-tree '$from' '$treeish'\n";
+ print "diff-tree $from $treeish\n";
my $pid = open my $diff_fh, '-|';
defined $pid or croak $!;
if ($pid == 0) {
@@ -506,7 +541,7 @@ sub svn_checkout_tree {
}
my $mods = parse_diff_tree($diff_fh);
unless (@$mods) {
- # git can do empty commits, SVN doesn't allow it...
+ # git can do empty commits, but SVN doesn't allow it...
return $mods;
}
my ($rm, $add) = precommit_check($mods);
@@ -593,7 +628,7 @@ sub svn_commit_tree {
my ($svn_rev, $commit) = @_;
my $commit_msg = "$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$";
my %log_msg = ( msg => '' );
- open my $msg, '>', $commit_msg or croak $!;
+ open my $msg, '>', $commit_msg or croak $!;
chomp(my $type = `git-cat-file -t $commit`);
if ($type eq 'commit') {
@@ -607,8 +642,10 @@ sub svn_commit_tree {
while (<$msg_fh>) {
if (!$in_msg) {
$in_msg = 1 if (/^\s*$/);
+ } elsif (/^git-svn-id: /) {
+ # skip this, we regenerate the correct one
+ # on re-fetch anyways
} else {
- $log_msg{msg} .= $_;
print $msg $_ or croak $!;
}
}
@@ -620,6 +657,15 @@ sub svn_commit_tree {
my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi';
system($editor, $commit_msg);
}
+
+ # file_to_s removes all trailing newlines, so just use chomp() here:
+ open $msg, '<', $commit_msg or croak $!;
+ { local $/; chomp($log_msg{msg} = <$msg>); }
+ close $msg or croak $!;
+
+ my ($oneline) = ($log_msg{msg} =~ /([^\n\r]+)/);
+ print "Committing $commit: $oneline\n";
+
my @ci_output = safe_qx(qw(svn commit -F),$commit_msg);
my ($committed) = grep(/^Committed revision \d+\./,@ci_output);
unlink $commit_msg;
@@ -711,6 +757,10 @@ sub svn_log_raw {
author => $author,
lines => $lines,
msg => '' );
+ if (defined $_authors && ! defined $users{$author}) {
+ die "Author: $author not defined in ",
+ "$_authors file\n";
+ }
push @svn_log, \%log_msg;
$state = 'msg_start';
next;
@@ -810,9 +860,9 @@ sub git_commit {
my ($log_msg, @parents) = @_;
assert_revision_unknown($log_msg->{revision});
my $out_fh = IO::File->new_tmpfile or croak $!;
- my $info = svn_info('.');
- my $uuid = $info->{'Repository UUID'};
- defined $uuid or croak "Unable to get Repository UUID\n";
+ $SVN_UUID ||= svn_info('.')->{'Repository UUID'};
+
+ map_tree_joins() if (@_branch_from && !%tree_map);
# commit parents can be conditionally bound to a particular
# svn revision via: "svn_revno=commit_sha1", filter them out here:
@@ -835,19 +885,26 @@ sub git_commit {
git_addremove();
chomp(my $tree = `git-write-tree`);
croak if $?;
+ if (exists $tree_map{$tree}) {
+ my %seen_parent = map { $_ => 1 } @exec_parents;
+ foreach (@{$tree_map{$tree}}) {
+ # MAXPARENT is defined to 16 in commit-tree.c:
+ if ($seen_parent{$_} || @exec_parents > 16) {
+ next;
+ }
+ push @exec_parents, $_;
+ $seen_parent{$_} = 1;
+ }
+ }
my $msg_fh = IO::File->new_tmpfile or croak $!;
print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ",
"$SVN_URL\@$log_msg->{revision}",
- " $uuid\n" or croak $!;
+ " $SVN_UUID\n" or croak $!;
$msg_fh->flush == 0 or croak $!;
seek $msg_fh, 0, 0 or croak $!;
- $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} =
- $log_msg->{author};
- $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} =
- $log_msg->{author}."\@$uuid";
- $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} =
- $log_msg->{date};
+ set_commit_env($log_msg);
+
my @exec = ('git-commit-tree',$tree);
push @exec, '-p', $_ foreach @exec_parents;
open STDIN, '<&', $msg_fh or croak $!;
@@ -863,7 +920,7 @@ sub git_commit {
if ($commit !~ /^$sha1$/o) {
croak "Failed to commit, invalid sha1: $commit\n";
}
- my @update_ref = ('git-update-ref',"refs/heads/$GIT_SVN-HEAD",$commit);
+ my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit);
if (my $primary_parent = shift @exec_parents) {
push @update_ref, $primary_parent;
}
@@ -873,6 +930,16 @@ sub git_commit {
return $commit;
}
+sub set_commit_env {
+ my ($log_msg) = @_;
+ my $author = $log_msg->{author};
+ my ($name,$email) = defined $users{$author} ? @{$users{$author}}
+ : ($author,"$author\@$SVN_UUID");
+ $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name;
+ $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email;
+ $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date};
+}
+
sub apply_mod_line_blob {
my $m = shift;
if ($m->{mode_b} =~ /^120/) {
@@ -936,6 +1003,63 @@ sub svn_check_ignore_externals {
$_no_ignore_ext = 1;
}
}
+
+sub check_upgrade_needed {
+ my $old = eval {
+ my $pid = open my $child, '-|';
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ close STDERR;
+ exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $?;
+ }
+ my @ret = (<$child>);
+ close $child or croak $?;
+ die $? if $?; # just in case close didn't error out
+ return wantarray ? @ret : join('',@ret);
+ };
+ return unless $old;
+ my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") };
+ if ($@ || !$head) {
+ print STDERR "Please run: $0 rebuild --upgrade\n";
+ exit 1;
+ }
+}
+
+# fills %tree_map with a reverse mapping of trees to commits. Useful
+# for finding parents to commit on.
+sub map_tree_joins {
+ foreach my $br (@_branch_from) {
+ my $pid = open my $pipe, '-|';
+ defined $pid or croak $!;
+ if ($pid == 0) {
+ exec(qw(git-rev-list --pretty=raw), $br) or croak $?;
+ }
+ while (<$pipe>) {
+ if (/^commit ($sha1)$/o) {
+ my $commit = $1;
+ my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o);
+ unless (defined $tree) {
+ die "Failed to parse commit $commit\n";
+ }
+ push @{$tree_map{$tree}}, $commit;
+ }
+ }
+ close $pipe or croak $?;
+ }
+}
+
+# '<svn username> = real-name <email address>' mapping based on git-svnimport:
+sub load_authors {
+ open my $authors, '<', $_authors or die "Can't open $_authors $!\n";
+ while (<$authors>) {
+ chomp;
+ next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
+ my ($user, $name, $email) = ($1, $2, $3);
+ $users{$user} = [$name, $email];
+ }
+ close $authors or croak $!;
+}
+
__END__
Data structures:
@@ -960,7 +1084,7 @@ diff-index line ($m hash)
mode_a => first column of diff-index output, no leading ':',
mode_b => second column of diff-index output,
sha1_b => sha1sum of the final blob,
- chg => change type [MCRAD],
+ chg => change type [MCRADT],
file_a => original file name of a file (iff chg is 'C' or 'R')
file_b => new/current file name of a file (any chg)
}
diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt
index b29073997c..8e9a971a85 100644
--- a/contrib/git-svn/git-svn.txt
+++ b/contrib/git-svn/git-svn.txt
@@ -27,7 +27,7 @@ For importing svn, git-svnimport is potentially more powerful when
operating on repositories organized under the recommended
trunk/branch/tags structure, and should be faster, too.
-git-svn completely ignores the very limited view of branching that
+git-svn mostly ignores the very limited view of branching that
Subversion has. This allows git-svn to be much easier to use,
especially on repositories that are not organized in a manner that
git-svnimport is designed for.
@@ -41,12 +41,12 @@ init::
fetch::
Fetch unfetched revisions from the SVN_URL we are tracking.
- refs/heads/git-svn-HEAD will be updated to the latest revision.
+ refs/heads/remotes/git-svn will be updated to the latest revision.
- Note: You should never attempt to modify the git-svn-HEAD branch
- outside of git-svn. Instead, create a branch from git-svn-HEAD
+ Note: You should never attempt to modify the remotes/git-svn branch
+ outside of git-svn. Instead, create a branch from remotes/git-svn
and work on that branch. Use the 'commit' command (see below)
- to write git commits back to git-svn-HEAD.
+ to write git commits back to remotes/git-svn.
commit::
Commit specified commit or tree objects to SVN. This relies on
@@ -116,8 +116,38 @@ OPTIONS
They are both passed directly to git-diff-tree see
git-diff-tree(1) for more information.
+ADVANCED OPTIONS
+----------------
+-b<refname>::
+--branch <refname>::
+ Used with 'fetch' or 'commit'.
+
+ This can be used to join arbitrary git branches to remotes/git-svn
+ on new commits where the tree object is equivalent.
+
+ When used with different GIT_SVN_ID values, tags and branches in
+ SVN can be tracked this way, as can some merges where the heads
+ end up having completely equivalent content. This can even be
+ used to track branches across multiple SVN _repositories_.
+
+ This option may be specified multiple times, once for each
+ branch.
+
+-i<GIT_SVN_ID>::
+--id <GIT_SVN_ID>::
+ This sets GIT_SVN_ID (instead of using the environment). See
+ the section on "Tracking Multiple Repositories or Branches" for
+ more information on using GIT_SVN_ID.
+
COMPATIBILITY OPTIONS
---------------------
+--upgrade::
+ Only used with the 'rebuild' command.
+
+ Run this if you used an old version of git-svn that used
+ 'git-svn-HEAD' instead of 'remotes/git-svn' as the branch
+ for tracking the remote.
+
--no-ignore-externals::
Only used with the 'fetch' and 'rebuild' command.
@@ -155,14 +185,14 @@ Tracking and contributing to an Subversion managed-project:
# Fetch remote revisions::
git-svn fetch
# Create your own branch to hack on::
- git checkout -b my-branch git-svn-HEAD
+ git checkout -b my-branch remotes/git-svn
# Commit only the git commits you want to SVN::
git-svn commit <tree-ish> [<tree-ish_2> ...]
# Commit all the git commits from my-branch that don't exist in SVN::
- git-svn commit git-svn-HEAD..my-branch
+ git-svn commit remotes/git-svn..my-branch
# Something is committed to SVN, pull the latest into your branch::
- git-svn fetch && git pull . git-svn-HEAD
-# Append svn:ignore settings to the default git exclude file:
+ git-svn fetch && git pull . remotes/git-svn
+# Append svn:ignore settings to the default git exclude file::
git-svn show-ignore >> .git/info/exclude
DESIGN PHILOSOPHY
@@ -184,8 +214,8 @@ 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
-invocation. The interface branch will be $GIT_SVN_ID-HEAD, instead of
-git-svn-HEAD. Any $GIT_SVN_ID-HEAD branch should never be modified
+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.
ADDITIONAL FETCH ARGUMENTS
diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh
index 181dfe008b..80ad3573db 100644
--- a/contrib/git-svn/t/t0000-contrib-git-svn.sh
+++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh
@@ -71,14 +71,14 @@ test_expect_success \
name='try a deep --rmdir with a commit'
-git checkout -b mybranch git-svn-HEAD
+git checkout -b mybranch remotes/git-svn
mv dir/a/b/c/d/e/file dir/file
cp dir/file file
git update-index --add --remove dir/a/b/c/d/e/file dir/file file
git commit -m "$name"
test_expect_success "$name" \
- "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch &&
+ "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch &&
test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a"
@@ -91,13 +91,13 @@ git update-index --add dir/file/file
git commit -m "$name"
test_expect_code 1 "$name" \
- 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch' \
+ 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \
|| true
name='detect node change from directory to file #1'
rm -rf dir $GIT_DIR/index
-git checkout -b mybranch2 git-svn-HEAD
+git checkout -b mybranch2 remotes/git-svn
mv bar/zzz zzz
rm -rf bar
mv zzz bar
@@ -106,13 +106,13 @@ git update-index --add -- bar
git commit -m "$name"
test_expect_code 1 "$name" \
- 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch2' \
+ 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \
|| true
name='detect node change from file to directory #2'
rm -f $GIT_DIR/index
-git checkout -b mybranch3 git-svn-HEAD
+git checkout -b mybranch3 remotes/git-svn
rm bar/zzz
git-update-index --remove bar/zzz
mkdir bar/zzz
@@ -121,13 +121,13 @@ git-update-index --add bar/zzz/yyy
git commit -m "$name"
test_expect_code 1 "$name" \
- 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch3' \
+ 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \
|| true
name='detect node change from directory to file #2'
rm -f $GIT_DIR/index
-git checkout -b mybranch4 git-svn-HEAD
+git checkout -b mybranch4 remotes/git-svn
rm -rf dir
git update-index --remove -- dir/file
touch dir
@@ -136,19 +136,19 @@ git update-index --add -- dir
git commit -m "$name"
test_expect_code 1 "$name" \
- 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch4' \
+ 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \
|| true
name='remove executable bit from a file'
rm -f $GIT_DIR/index
-git checkout -b mybranch5 git-svn-HEAD
+git checkout -b mybranch5 remotes/git-svn
chmod -x exec.sh
git update-index exec.sh
git commit -m "$name"
test_expect_success "$name" \
- "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+ "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
test ! -x $SVN_TREE/exec.sh"
@@ -158,7 +158,7 @@ git update-index exec.sh
git commit -m "$name"
test_expect_success "$name" \
- "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+ "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
test -x $SVN_TREE/exec.sh"
@@ -170,7 +170,7 @@ git update-index exec.sh
git commit -m "$name"
test_expect_success "$name" \
- "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+ "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
test -L $SVN_TREE/exec.sh"
@@ -182,7 +182,7 @@ git update-index --add bar/zzz exec-2.sh
git commit -m "$name"
test_expect_success "$name" \
- "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+ "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
test -x $SVN_TREE/bar/zzz &&
test -L $SVN_TREE/exec-2.sh"
@@ -196,7 +196,7 @@ git update-index exec-2.sh
git commit -m "$name"
test_expect_success "$name" \
- "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 &&
+ "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 &&
test -f $SVN_TREE/exec-2.sh &&
test ! -L $SVN_TREE/exec-2.sh &&
diff -u help $SVN_TREE/exec-2.sh"
@@ -207,9 +207,9 @@ name='test fetch functionality (svn => git) with alternate GIT_SVN_ID'
GIT_SVN_ID=alt
export GIT_SVN_ID
test_expect_success "$name" \
- "git-svn init $svnrepo && git-svn fetch -v &&
- git-rev-list --pretty=raw git-svn-HEAD | grep ^tree | uniq > a &&
- git-rev-list --pretty=raw alt-HEAD | grep ^tree | uniq > b &&
+ "git-svn init $svnrepo && git-svn fetch &&
+ git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a &&
+ git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b &&
diff -u a b"
test_done
diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview
index ea05cd4240..781badbc5b 100755
--- a/contrib/gitview/gitview
+++ b/contrib/gitview/gitview
@@ -513,7 +513,7 @@ class GitView:
scrollwin = gtk.ScrolledWindow()
- scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
+ scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrollwin.set_shadow_type(gtk.SHADOW_IN)
vbox.pack_start(scrollwin, expand=True, fill=True)
scrollwin.show()
@@ -526,9 +526,6 @@ class GitView:
self.treeview.show()
cell = CellRendererGraph()
- # Set the default width to 265
- # This make sure that we have nice display with large tag names
- cell.set_property("width", 265)
column = gtk.TreeViewColumn()
column.set_resizable(True)
column.pack_start(cell, expand=True)
@@ -801,7 +798,7 @@ class GitView:
button.set_relief(gtk.RELIEF_NONE)
button.set_sensitive(True)
button.connect("clicked", self._show_clicked_cb,
- child_id, commit.commit_sha1)
+ child_id, commit.commit_sha1, self.encoding)
hbox.pack_start(button, expand=False, fill=True)
button.show()
diff --git a/count-delta.c b/count-delta.c
deleted file mode 100644
index 058a2aadb1..0000000000
--- a/count-delta.c
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2005 Junio C Hamano
- * The delta-parsing part is almost straight copy of patch-delta.c
- * which is (C) 2005 Nicolas Pitre <nico@cam.org>.
- */
-#include <stdlib.h>
-#include <string.h>
-#include <limits.h>
-#include "delta.h"
-#include "count-delta.h"
-
-/*
- * NOTE. We do not _interpret_ delta fully. As an approximation, we
- * just count the number of bytes that are copied from the source, and
- * the number of literal data bytes that are inserted.
- *
- * Number of bytes that are _not_ copied from the source is deletion,
- * and number of inserted literal bytes are addition, so sum of them
- * is the extent of damage.
- */
-int count_delta(void *delta_buf, unsigned long delta_size,
- unsigned long *src_copied, unsigned long *literal_added)
-{
- unsigned long copied_from_source, added_literal;
- const unsigned char *data, *top;
- unsigned char cmd;
- unsigned long src_size, dst_size, out;
-
- if (delta_size < DELTA_SIZE_MIN)
- return -1;
-
- data = delta_buf;
- top = delta_buf + delta_size;
-
- src_size = get_delta_hdr_size(&data);
- dst_size = get_delta_hdr_size(&data);
-
- added_literal = copied_from_source = out = 0;
- while (data < top) {
- cmd = *data++;
- if (cmd & 0x80) {
- unsigned long cp_off = 0, cp_size = 0;
- if (cmd & 0x01) cp_off = *data++;
- if (cmd & 0x02) cp_off |= (*data++ << 8);
- if (cmd & 0x04) cp_off |= (*data++ << 16);
- if (cmd & 0x08) cp_off |= (*data++ << 24);
- if (cmd & 0x10) cp_size = *data++;
- if (cmd & 0x20) cp_size |= (*data++ << 8);
- if (cmd & 0x40) cp_size |= (*data++ << 16);
- if (cp_size == 0) cp_size = 0x10000;
-
- copied_from_source += cp_size;
- out += cp_size;
- } else {
- /* write literal into dst */
- added_literal += cmd;
- out += cmd;
- data += cmd;
- }
- }
-
- /* sanity check */
- if (data != top || out != dst_size)
- return -1;
-
- /* delete size is what was _not_ copied from source.
- * edit size is that and literal additions.
- */
- *src_copied = copied_from_source;
- *literal_added = added_literal;
- return 0;
-}
diff --git a/count-delta.h b/count-delta.h
deleted file mode 100644
index 7359629827..0000000000
--- a/count-delta.h
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright (C) 2005 Junio C Hamano
- */
-#ifndef COUNT_DELTA_H
-#define COUNT_DELTA_H
-
-int count_delta(void *, unsigned long,
- unsigned long *src_copied, unsigned long *literal_added);
-
-#endif
diff --git a/diff-delta.c b/diff-delta.c
index c2f656ae39..2ed5984b1c 100644
--- a/diff-delta.c
+++ b/diff-delta.c
@@ -19,8 +19,9 @@
*/
#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
#include "delta.h"
-#include "zlib.h"
/* block size: min = 16, max = 64k, power of 2 */
@@ -29,149 +30,87 @@
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define GR_PRIME 0x9e370001
-#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b)))
-
-static unsigned int hashbits(unsigned int size)
-{
- unsigned int val = 1, bits = 0;
- while (val < size && bits < 32) {
- val <<= 1;
- bits++;
- }
- return bits ? bits: 1;
-}
-
-typedef struct s_chanode {
- struct s_chanode *next;
- int icurr;
-} chanode_t;
-
-typedef struct s_chastore {
- int isize, nsize;
- chanode_t *ancur;
-} chastore_t;
-
-static void cha_init(chastore_t *cha, int isize, int icount)
-{
- cha->isize = isize;
- cha->nsize = icount * isize;
- cha->ancur = NULL;
-}
-
-static void *cha_alloc(chastore_t *cha)
-{
- chanode_t *ancur;
- void *data;
+#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift))
- ancur = cha->ancur;
- if (!ancur || ancur->icurr == cha->nsize) {
- ancur = malloc(sizeof(chanode_t) + cha->nsize);
- if (!ancur)
- return NULL;
- ancur->icurr = 0;
- ancur->next = cha->ancur;
- cha->ancur = ancur;
- }
-
- data = (void *)ancur + sizeof(chanode_t) + ancur->icurr;
- ancur->icurr += cha->isize;
- return data;
-}
-
-static void cha_free(chastore_t *cha)
-{
- chanode_t *cur = cha->ancur;
- while (cur) {
- chanode_t *tmp = cur;
- cur = cur->next;
- free(tmp);
- }
-}
-
-typedef struct s_bdrecord {
- struct s_bdrecord *next;
- unsigned int fp;
+struct index {
const unsigned char *ptr;
-} bdrecord_t;
-
-typedef struct s_bdfile {
- chastore_t cha;
- unsigned int fphbits;
- bdrecord_t **fphash;
-} bdfile_t;
+ unsigned int val;
+ struct index *next;
+};
-static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf)
+static struct index ** delta_index(const unsigned char *buf,
+ unsigned long bufsize,
+ unsigned int *hash_shift)
{
- unsigned int fphbits;
- int i, hsize;
- const unsigned char *data, *top;
- bdrecord_t *brec;
- bdrecord_t **fphash;
-
- fphbits = hashbits(bufsize / BLK_SIZE + 1);
- hsize = 1 << fphbits;
- fphash = malloc(hsize * sizeof(bdrecord_t *));
- if (!fphash)
- return -1;
- for (i = 0; i < hsize; i++)
- fphash[i] = NULL;
- cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1);
-
- top = buf + bufsize;
- data = buf + (bufsize / BLK_SIZE) * BLK_SIZE;
- if (data == top)
+ unsigned int hsize, hshift, entries, blksize, i;
+ const unsigned char *data;
+ struct index *entry, **hash;
+ void *mem;
+
+ /* determine index hash size */
+ entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE;
+ hsize = entries / 4;
+ for (i = 4; (1 << i) < hsize && i < 16; i++);
+ hsize = 1 << i;
+ hshift = 32 - i;
+ *hash_shift = hshift;
+
+ /* allocate lookup index */
+ mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry));
+ if (!mem)
+ return NULL;
+ hash = mem;
+ entry = mem + hsize * sizeof(*hash);
+ memset(hash, 0, hsize * sizeof(*hash));
+
+ /* then populate it */
+ data = buf + entries * BLK_SIZE - BLK_SIZE;
+ blksize = bufsize - (data - buf);
+ while (data >= buf) {
+ unsigned int val = adler32(0, data, blksize);
+ i = HASH(val, hshift);
+ entry->ptr = data;
+ entry->val = val;
+ entry->next = hash[i];
+ hash[i] = entry++;
+ blksize = BLK_SIZE;
data -= BLK_SIZE;
+ }
- for ( ; data >= buf; data -= BLK_SIZE) {
- brec = cha_alloc(&bdf->cha);
- if (!brec) {
- cha_free(&bdf->cha);
- free(fphash);
- return -1;
- }
- brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data));
- brec->ptr = data;
- i = HASH(brec->fp, fphbits);
- brec->next = fphash[i];
- fphash[i] = brec;
- }
-
- bdf->fphbits = fphbits;
- bdf->fphash = fphash;
-
- return 0;
-}
-
-static void delta_cleanup(bdfile_t *bdf)
-{
- free(bdf->fphash);
- cha_free(&bdf->cha);
+ return hash;
}
+/* provide the size of the copy opcode given the block offset and size */
#define COPYOP_SIZE(o, s) \
(!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \
!!(s & 0xff) + !!(s & 0xff00) + 1)
+/* the maximum size for any opcode */
+#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff)
+
void *diff_delta(void *from_buf, unsigned long from_size,
void *to_buf, unsigned long to_size,
unsigned long *delta_size,
unsigned long max_size)
{
- int i, outpos, outsize, inscnt, csize, msize, moff;
- unsigned int fp;
- const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2;
- unsigned char *out, *orig;
- bdrecord_t *brec;
- bdfile_t bdf;
+ unsigned int i, outpos, outsize, inscnt, hash_shift;
+ const unsigned char *ref_data, *ref_top, *data, *top;
+ unsigned char *out;
+ struct index *entry, **hash;
- if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf))
+ if (!from_size || !to_size)
+ return NULL;
+ hash = delta_index(from_buf, from_size, &hash_shift);
+ if (!hash)
return NULL;
-
+
outpos = 0;
outsize = 8192;
+ if (max_size && outsize >= max_size)
+ outsize = max_size + MAX_OP_SIZE + 1;
out = malloc(outsize);
if (!out) {
- delta_cleanup(&bdf);
+ free(hash);
return NULL;
}
@@ -199,28 +138,32 @@ void *diff_delta(void *from_buf, unsigned long from_size,
}
inscnt = 0;
- moff = 0;
- while (data < top) {
- msize = 0;
- fp = adler32(0, data, MIN(top - data, BLK_SIZE));
- i = HASH(fp, bdf.fphbits);
- for (brec = bdf.fphash[i]; brec; brec = brec->next) {
- if (brec->fp == fp) {
- csize = ref_top - brec->ptr;
- if (csize > top - data)
- csize = top - data;
- for (ptr1 = brec->ptr, ptr2 = data;
- csize && *ptr1 == *ptr2;
- csize--, ptr1++, ptr2++);
- csize = ptr1 - brec->ptr;
- if (csize > msize) {
- moff = brec->ptr - ref_data;
- msize = csize;
- if (msize >= 0x10000) {
- msize = 0x10000;
- break;
- }
+ while (data < top) {
+ unsigned int moff = 0, msize = 0;
+ unsigned int blksize = MIN(top - data, BLK_SIZE);
+ unsigned int val = adler32(0, data, blksize);
+ i = HASH(val, hash_shift);
+ for (entry = hash[i]; entry; entry = entry->next) {
+ const unsigned char *ref = entry->ptr;
+ const unsigned char *src = data;
+ unsigned int ref_size = ref_top - ref;
+ if (entry->val != val)
+ continue;
+ if (ref_size > top - src)
+ ref_size = top - src;
+ while (ref_size && *src++ == *ref) {
+ ref++;
+ ref_size--;
+ }
+ ref_size = ref - entry->ptr;
+ if (ref_size > msize) {
+ /* this is our best match so far */
+ moff = entry->ptr - ref_data;
+ msize = ref_size;
+ if (msize >= 0x10000) {
+ msize = 0x10000;
+ break;
}
}
}
@@ -235,13 +178,15 @@ void *diff_delta(void *from_buf, unsigned long from_size,
inscnt = 0;
}
} else {
+ unsigned char *op;
+
if (inscnt) {
out[outpos - inscnt - 1] = inscnt;
inscnt = 0;
}
data += msize;
- orig = out + outpos++;
+ op = out + outpos++;
i = 0x80;
if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; }
@@ -256,23 +201,21 @@ void *diff_delta(void *from_buf, unsigned long from_size,
msize >>= 8;
if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; }
- *orig = i;
- }
-
- if (max_size && outpos > max_size) {
- free(out);
- delta_cleanup(&bdf);
- return NULL;
+ *op = i;
}
- /* next time around the largest possible output is 1 + 4 + 3 */
- if (outpos > outsize - 8) {
+ if (outpos >= outsize - MAX_OP_SIZE) {
void *tmp = out;
outsize = outsize * 3 / 2;
- out = realloc(out, outsize);
+ if (max_size && outsize >= max_size)
+ outsize = max_size + MAX_OP_SIZE + 1;
+ if (max_size && outpos > max_size)
+ out = NULL;
+ else
+ out = realloc(out, outsize);
if (!out) {
free(tmp);
- delta_cleanup(&bdf);
+ free(hash);
return NULL;
}
}
@@ -281,7 +224,7 @@ void *diff_delta(void *from_buf, unsigned long from_size,
if (inscnt)
out[outpos - inscnt - 1] = inscnt;
- delta_cleanup(&bdf);
+ free(hash);
*delta_size = outpos;
return out;
}
diff --git a/diffcore-break.c b/diffcore-break.c
index 0fc2b860be..71ad58a25a 100644
--- a/diffcore-break.c
+++ b/diffcore-break.c
@@ -45,8 +45,8 @@ static int should_break(struct diff_filespec *src,
* The value we return is 1 if we want the pair to be broken,
* or 0 if we do not.
*/
- unsigned long delta_size, base_size, src_copied, literal_added;
- int to_break = 0;
+ unsigned long delta_size, base_size, src_copied, literal_added,
+ src_removed;
*merge_score_p = 0; /* assume no deletion --- "do not break"
* is the default.
@@ -72,33 +72,40 @@ static int should_break(struct diff_filespec *src,
&src_copied, &literal_added))
return 0;
+ /* sanity */
+ if (src->size < src_copied)
+ src_copied = src->size;
+ if (dst->size < literal_added + src_copied) {
+ if (src_copied < dst->size)
+ literal_added = dst->size - src_copied;
+ else
+ literal_added = 0;
+ }
+ src_removed = src->size - src_copied;
+
/* Compute merge-score, which is "how much is removed
* from the source material". The clean-up stage will
* merge the surviving pair together if the score is
* less than the minimum, after rename/copy runs.
*/
- if (src->size <= src_copied)
- ; /* all copied, nothing removed */
- else {
- delta_size = src->size - src_copied;
- *merge_score_p = delta_size * MAX_SCORE / src->size;
- }
-
+ *merge_score_p = src_removed * MAX_SCORE / src->size;
+
/* Extent of damage, which counts both inserts and
* deletes.
*/
- if (src->size + literal_added <= src_copied)
- delta_size = 0; /* avoid wrapping around */
- else
- delta_size = (src->size - src_copied) + literal_added;
-
- /* We break if the edit exceeds the minimum.
- * i.e. (break_score / MAX_SCORE < delta_size / base_size)
+ delta_size = src_removed + literal_added;
+ if (delta_size * MAX_SCORE / base_size < break_score)
+ return 0;
+
+ /* If you removed a lot without adding new material, that is
+ * not really a rewrite.
*/
- if (break_score * base_size < delta_size * MAX_SCORE)
- to_break = 1;
+ if ((src->size * break_score < src_removed * MAX_SCORE) &&
+ (literal_added * 20 < src_removed) &&
+ (literal_added * 20 < src_copied))
+ return 0;
- return to_break;
+ return 1;
}
void diffcore_break(int break_score)
diff --git a/diffcore-delta.c b/diffcore-delta.c
index d03787be65..70bacff837 100644
--- a/diffcore-delta.c
+++ b/diffcore-delta.c
@@ -2,87 +2,52 @@
#include "diff.h"
#include "diffcore.h"
-struct linehash {
- unsigned long bytes;
- unsigned long hash;
-};
+/*
+ * Idea here is very simple.
+ *
+ * We have total of (sz-N+1) N-byte overlapping sequences in buf whose
+ * size is sz. If the same N-byte sequence appears in both source and
+ * destination, we say the byte that starts that sequence is shared
+ * between them (i.e. copied from source to destination).
+ *
+ * For each possible N-byte sequence, if the source buffer has more
+ * instances of it than the destination buffer, that means the
+ * difference are the number of bytes not copied from source to
+ * destination. If the counts are the same, everything was copied
+ * from source to destination. If the destination has more,
+ * everything was copied, and destination added more.
+ *
+ * We are doing an approximation so we do not really have to waste
+ * memory by actually storing the sequence. We just hash them into
+ * somewhere around 2^16 hashbuckets and count the occurrences.
+ *
+ * The length of the sequence is arbitrarily set to 8 for now.
+ */
-static unsigned long hash_extended_line(const unsigned char **buf_p,
- unsigned long left)
-{
- /* An extended line is zero or more whitespace letters (including LF)
- * followed by one non whitespace letter followed by zero or more
- * non LF, and terminated with by a LF (or EOF).
- */
- const unsigned char *bol = *buf_p;
- const unsigned char *buf = bol;
- unsigned long hashval = 0;
- while (left) {
- unsigned c = *buf++;
- if (!c)
- goto binary;
- left--;
- if (' ' < c) {
- hashval = c;
- break;
- }
- }
- while (left) {
- unsigned c = *buf++;
- if (!c)
- goto binary;
- left--;
- if (c == '\n')
- break;
- if (' ' < c)
- hashval = hashval * 11 + c;
- }
- *buf_p = buf;
- return hashval;
-
- binary:
- *buf_p = NULL;
- return 0;
-}
-
-static int linehash_compare(const void *a_, const void *b_)
-{
- struct linehash *a = (struct linehash *) a_;
- struct linehash *b = (struct linehash *) b_;
- if (a->hash < b->hash) return -1;
- if (a->hash > b->hash) return 1;
- return 0;
-}
+#define HASHBASE 65537 /* next_prime(2^16) */
-static struct linehash *hash_lines(const unsigned char *buf,
- unsigned long size)
+static void hash_chars(unsigned char *buf, unsigned long sz, int *count)
{
- const unsigned char *eobuf = buf + size;
- struct linehash *line = NULL;
- int alloc = 0, used = 0;
+ unsigned int accum1, accum2, i;
- while (buf < eobuf) {
- const unsigned char *ptr = buf;
- unsigned long hash = hash_extended_line(&buf, eobuf-ptr);
- if (!buf) {
- free(line);
- return NULL;
- }
- if (alloc <= used) {
- alloc = alloc_nr(alloc);
- line = xrealloc(line, sizeof(*line) * alloc);
- }
- line[used].bytes = buf - ptr;
- line[used].hash = hash;
- used++;
+ /* an 8-byte shift register made of accum1 and accum2. New
+ * bytes come at LSB of accum2, and shifted up to accum1
+ */
+ for (i = accum1 = accum2 = 0; i < 7; i++, sz--) {
+ accum1 = (accum1 << 8) | (accum2 >> 24);
+ accum2 = (accum2 << 8) | *buf++;
+ }
+ while (sz) {
+ accum1 = (accum1 << 8) | (accum2 >> 24);
+ accum2 = (accum2 << 8) | *buf++;
+ /* We want something that hashes permuted byte
+ * sequences nicely; simpler hash like (accum1 ^
+ * accum2) does not perform as well.
+ */
+ i = (accum1 + accum2 * 0x61) % HASHBASE;
+ count[i]++;
+ sz--;
}
- qsort(line, used, sizeof(*line), linehash_compare);
-
- /* Terminate the list */
- if (alloc <= used)
- line = xrealloc(line, sizeof(*line) * (used+1));
- line[used].bytes = line[used].hash = 0;
- return line;
}
int diffcore_count_changes(void *src, unsigned long src_size,
@@ -91,38 +56,28 @@ int diffcore_count_changes(void *src, unsigned long src_size,
unsigned long *src_copied,
unsigned long *literal_added)
{
- struct linehash *src_lines, *dst_lines;
+ int *src_count, *dst_count, i;
unsigned long sc, la;
- src_lines = hash_lines(src, src_size);
- if (!src_lines)
- return -1;
- dst_lines = hash_lines(dst, dst_size);
- if (!dst_lines) {
- free(src_lines);
+ if (src_size < 8 || dst_size < 8)
return -1;
- }
+
+ src_count = xcalloc(HASHBASE * 2, sizeof(int));
+ dst_count = src_count + HASHBASE;
+ hash_chars(src, src_size, src_count);
+ hash_chars(dst, dst_size, dst_count);
+
sc = la = 0;
- while (src_lines->bytes && dst_lines->bytes) {
- int cmp = linehash_compare(src_lines, dst_lines);
- if (!cmp) {
- sc += src_lines->bytes;
- src_lines++;
- dst_lines++;
- continue;
+ for (i = 0; i < HASHBASE; i++) {
+ if (src_count[i] < dst_count[i]) {
+ la += dst_count[i] - src_count[i];
+ sc += src_count[i];
}
- if (cmp < 0) {
- src_lines++;
- continue;
- }
- la += dst_lines->bytes;
- dst_lines++;
- }
- while (dst_lines->bytes) {
- la += dst_lines->bytes;
- dst_lines++;
+ else /* i.e. if (dst_count[i] <= src_count[i]) */
+ sc += dst_count[i];
}
*src_copied = sc;
*literal_added = la;
+ free(src_count);
return 0;
}
diff --git a/diffcore.h b/diffcore.h
index dba4f17658..d31b3b476c 100644
--- a/diffcore.h
+++ b/diffcore.h
@@ -17,8 +17,8 @@
*/
#define MAX_SCORE 60000.0
#define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */
-#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%)*/
-#define DEFAULT_MERGE_SCORE 48000 /* maximum for break-merge to happen (80%)*/
+#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%) */
+#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen 60%) */
#define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */
diff --git a/environment.c b/environment.c
index 251e53ca09..16c08f0697 100644
--- a/environment.c
+++ b/environment.c
@@ -17,6 +17,7 @@ int only_use_symrefs = 0;
int repository_format_version = 0;
char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8";
int shared_repository = 0;
+const char *apply_default_whitespace = NULL;
static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir,
*git_graft_file;
diff --git a/epoch.c b/epoch.c
deleted file mode 100644
index 3a767486da..0000000000
--- a/epoch.c
+++ /dev/null
@@ -1,639 +0,0 @@
-/*
- * Copyright (c) 2005, Jon Seymour
- *
- * For more information about epoch theory on which this module is based,
- * refer to http://blackcubes.dyndns.org/epoch/. That web page defines
- * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales
- * for some of the algorithms used here.
- *
- */
-#include <stdlib.h>
-
-/* Provides arbitrary precision integers required to accurately represent
- * fractional mass: */
-#include <openssl/bn.h>
-
-#include "cache.h"
-#include "commit.h"
-#include "epoch.h"
-
-struct fraction {
- BIGNUM numerator;
- BIGNUM denominator;
-};
-
-#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next)
-
-static BN_CTX *context = NULL;
-static struct fraction *one = NULL;
-static struct fraction *zero = NULL;
-
-static BN_CTX *get_BN_CTX(void)
-{
- if (!context) {
- context = BN_CTX_new();
- }
- return context;
-}
-
-static struct fraction *new_zero(void)
-{
- struct fraction *result = xmalloc(sizeof(*result));
- BN_init(&result->numerator);
- BN_init(&result->denominator);
- BN_zero(&result->numerator);
- BN_one(&result->denominator);
- return result;
-}
-
-static void clear_fraction(struct fraction *fraction)
-{
- BN_clear(&fraction->numerator);
- BN_clear(&fraction->denominator);
-}
-
-static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor)
-{
- BIGNUM bn_divisor;
-
- BN_init(&bn_divisor);
- BN_set_word(&bn_divisor, divisor);
-
- BN_copy(&result->numerator, &fraction->numerator);
- BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX());
-
- BN_clear(&bn_divisor);
- return result;
-}
-
-static struct fraction *init_fraction(struct fraction *fraction)
-{
- BN_init(&fraction->numerator);
- BN_init(&fraction->denominator);
- BN_zero(&fraction->numerator);
- BN_one(&fraction->denominator);
- return fraction;
-}
-
-static struct fraction *get_one(void)
-{
- if (!one) {
- one = new_zero();
- BN_one(&one->numerator);
- }
- return one;
-}
-
-static struct fraction *get_zero(void)
-{
- if (!zero) {
- zero = new_zero();
- }
- return zero;
-}
-
-static struct fraction *copy(struct fraction *to, struct fraction *from)
-{
- BN_copy(&to->numerator, &from->numerator);
- BN_copy(&to->denominator, &from->denominator);
- return to;
-}
-
-static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right)
-{
- BIGNUM a, b, gcd;
-
- BN_init(&a);
- BN_init(&b);
- BN_init(&gcd);
-
- BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
- BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
- BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX());
- BN_add(&result->numerator, &a, &b);
-
- BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX());
- BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX());
- BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX());
-
- BN_clear(&a);
- BN_clear(&b);
- BN_clear(&gcd);
-
- return result;
-}
-
-static int compare(struct fraction *left, struct fraction *right)
-{
- BIGNUM a, b;
- int result;
-
- BN_init(&a);
- BN_init(&b);
-
- BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX());
- BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX());
-
- result = BN_cmp(&a, &b);
-
- BN_clear(&a);
- BN_clear(&b);
-
- return result;
-}
-
-struct mass_counter {
- struct fraction seen;
- struct fraction pending;
-};
-
-static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending)
-{
- struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter));
- memset(mass_counter, 0, sizeof(*mass_counter));
-
- init_fraction(&mass_counter->seen);
- init_fraction(&mass_counter->pending);
-
- copy(&mass_counter->pending, pending);
- copy(&mass_counter->seen, get_zero());
-
- if (commit->object.util) {
- die("multiple attempts to initialize mass counter for %s",
- sha1_to_hex(commit->object.sha1));
- }
-
- commit->object.util = mass_counter;
-
- return mass_counter;
-}
-
-static void free_mass_counter(struct mass_counter *counter)
-{
- clear_fraction(&counter->seen);
- clear_fraction(&counter->pending);
- free(counter);
-}
-
-/*
- * Finds the base commit of a list of commits.
- *
- * One property of the commit being searched for is that every commit reachable
- * from the base commit is reachable from the commits in the starting list only
- * via paths that include the base commit.
- *
- * This algorithm uses a conservation of mass approach to find the base commit.
- *
- * We start by injecting one unit of mass into the graph at each
- * of the commits in the starting list. Injecting mass into a commit
- * is achieved by adding to its pending mass counter and, if it is not already
- * enqueued, enqueuing the commit in a list of pending commits, in latest
- * commit date first order.
- *
- * The algorithm then proceeds to visit each commit in the pending queue.
- * Upon each visit, the pending mass is added to the mass already seen for that
- * commit and then divided into N equal portions, where N is the number of
- * parents of the commit being visited. The divided portions are then injected
- * into each of the parents.
- *
- * The algorithm continues until we discover a commit which has seen all the
- * mass originally injected or until we run out of things to do.
- *
- * If we find a commit that has seen all the original mass, we have found
- * the common base of all the commits in the starting list.
- *
- * The algorithm does _not_ depend on accurate timestamps for correct operation.
- * However, reasonably sane (e.g. non-random) timestamps are required in order
- * to prevent an exponential performance characteristic. The occasional
- * timestamp inaccuracy will not dramatically affect performance but may
- * result in more nodes being processed than strictly necessary.
- *
- * This procedure sets *boundary to the address of the base commit. It returns
- * non-zero if, and only if, there was a problem parsing one of the
- * commits discovered during the traversal.
- */
-static int find_base_for_list(struct commit_list *list, struct commit **boundary)
-{
- int ret = 0;
- struct commit_list *cleaner = NULL;
- struct commit_list *pending = NULL;
- struct fraction injected;
- init_fraction(&injected);
- *boundary = NULL;
-
- for (; list; list = list->next) {
- struct commit *item = list->item;
-
- if (!item->object.util) {
- new_mass_counter(list->item, get_one());
- add(&injected, &injected, get_one());
-
- commit_list_insert(list->item, &cleaner);
- commit_list_insert(list->item, &pending);
- }
- }
-
- while (!*boundary && pending && !ret) {
- struct commit *latest = pop_commit(&pending);
- struct mass_counter *latest_node = (struct mass_counter *) latest->object.util;
- int num_parents;
-
- if ((ret = parse_commit(latest)))
- continue;
- add(&latest_node->seen, &latest_node->seen, &latest_node->pending);
-
- num_parents = count_parents(latest);
- if (num_parents) {
- struct fraction distribution;
- struct commit_list *parents;
-
- divide(init_fraction(&distribution), &latest_node->pending, num_parents);
-
- for (parents = latest->parents; parents; parents = parents->next) {
- struct commit *parent = parents->item;
- struct mass_counter *parent_node = (struct mass_counter *) parent->object.util;
-
- if (!parent_node) {
- parent_node = new_mass_counter(parent, &distribution);
- insert_by_date(parent, &pending);
- commit_list_insert(parent, &cleaner);
- } else {
- if (!compare(&parent_node->pending, get_zero()))
- insert_by_date(parent, &pending);
- add(&parent_node->pending, &parent_node->pending, &distribution);
- }
- }
-
- clear_fraction(&distribution);
- }
-
- if (!compare(&latest_node->seen, &injected))
- *boundary = latest;
- copy(&latest_node->pending, get_zero());
- }
-
- while (cleaner) {
- struct commit *next = pop_commit(&cleaner);
- free_mass_counter((struct mass_counter *) next->object.util);
- next->object.util = NULL;
- }
-
- if (pending)
- free_commit_list(pending);
-
- clear_fraction(&injected);
- return ret;
-}
-
-
-/*
- * Finds the base of an minimal, non-linear epoch, headed at head, by
- * applying the find_base_for_list to a list consisting of the parents
- */
-static int find_base(struct commit *head, struct commit **boundary)
-{
- int ret = 0;
- struct commit_list *pending = NULL;
- struct commit_list *next;
-
- for (next = head->parents; next; next = next->next) {
- commit_list_insert(next->item, &pending);
- }
- ret = find_base_for_list(pending, boundary);
- free_commit_list(pending);
-
- return ret;
-}
-
-/*
- * This procedure traverses to the boundary of the first epoch in the epoch
- * sequence of the epoch headed at head_of_epoch. This is either the end of
- * the maximal linear epoch or the base of a minimal non-linear epoch.
- *
- * The queue of pending nodes is sorted in reverse date order and each node
- * is currently in the queue at most once.
- */
-static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary)
-{
- int ret;
- struct commit *item = head_of_epoch;
-
- ret = parse_commit(item);
- if (ret)
- return ret;
-
- if (HAS_EXACTLY_ONE_PARENT(item)) {
- /*
- * We are at the start of a maximimal linear epoch.
- * Traverse to the end.
- */
- while (HAS_EXACTLY_ONE_PARENT(item) && !ret) {
- item = item->parents->item;
- ret = parse_commit(item);
- }
- *boundary = item;
-
- } else {
- /*
- * Otherwise, we are at the start of a minimal, non-linear
- * epoch - find the common base of all parents.
- */
- ret = find_base(item, boundary);
- }
-
- return ret;
-}
-
-/*
- * Returns non-zero if parent is known to be a parent of child.
- */
-static int is_parent_of(struct commit *parent, struct commit *child)
-{
- struct commit_list *parents;
- for (parents = child->parents; parents; parents = parents->next) {
- if (!memcmp(parent->object.sha1, parents->item->object.sha1,
- sizeof(parents->item->object.sha1)))
- return 1;
- }
- return 0;
-}
-
-/*
- * Pushes an item onto the merge order stack. If the top of the stack is
- * marked as being a possible "break", we check to see whether it actually
- * is a break.
- */
-static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item)
-{
- struct commit_list *top = *stack;
- if (top && (top->item->object.flags & DISCONTINUITY)) {
- if (is_parent_of(top->item, item)) {
- top->item->object.flags &= ~DISCONTINUITY;
- }
- }
- commit_list_insert(item, stack);
-}
-
-/*
- * Marks all interesting, visited commits reachable from this commit
- * as uninteresting. We stop recursing when we reach the epoch boundary,
- * an unvisited node or a node that has already been marking uninteresting.
- *
- * This doesn't actually mark all ancestors between the start node and the
- * epoch boundary uninteresting, but does ensure that they will eventually
- * be marked uninteresting when the main sort_first_epoch() traversal
- * eventually reaches them.
- */
-static void mark_ancestors_uninteresting(struct commit *commit)
-{
- unsigned int flags = commit->object.flags;
- int visited = flags & VISITED;
- int boundary = flags & BOUNDARY;
- int uninteresting = flags & UNINTERESTING;
- struct commit_list *next;
-
- commit->object.flags |= UNINTERESTING;
-
- /*
- * We only need to recurse if
- * we are not on the boundary and
- * we have not already been marked uninteresting and
- * we have already been visited.
- *
- * The main sort_first_epoch traverse will mark unreachable
- * all uninteresting, unvisited parents as they are visited
- * so there is no need to duplicate that traversal here.
- *
- * Similarly, if we are already marked uninteresting
- * then either all ancestors have already been marked
- * uninteresting or will be once the sort_first_epoch
- * traverse reaches them.
- */
-
- if (uninteresting || boundary || !visited)
- return;
-
- for (next = commit->parents; next; next = next->next)
- mark_ancestors_uninteresting(next->item);
-}
-
-/*
- * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head
- * into merge order.
- */
-static void sort_first_epoch(struct commit *head, struct commit_list **stack)
-{
- struct commit_list *parents;
-
- head->object.flags |= VISITED;
-
- /*
- * TODO: By sorting the parents in a different order, we can alter the
- * merge order to show contemporaneous changes in parallel branches
- * occurring after "local" changes. This is useful for a developer
- * when a developer wants to see all changes that were incorporated
- * into the same merge as her own changes occur after her own
- * changes.
- */
-
- for (parents = head->parents; parents; parents = parents->next) {
- struct commit *parent = parents->item;
-
- if (head->object.flags & UNINTERESTING) {
- /*
- * Propagates the uninteresting bit to all parents.
- * if we have already visited this parent, then
- * the uninteresting bit will be propagated to each
- * reachable commit that is still not marked
- * uninteresting and won't otherwise be reached.
- */
- mark_ancestors_uninteresting(parent);
- }
-
- if (!(parent->object.flags & VISITED)) {
- if (parent->object.flags & BOUNDARY) {
- if (*stack) {
- die("something else is on the stack - %s",
- sha1_to_hex((*stack)->item->object.sha1));
- }
- push_onto_merge_order_stack(stack, parent);
- parent->object.flags |= VISITED;
-
- } else {
- sort_first_epoch(parent, stack);
- if (parents) {
- /*
- * This indicates a possible
- * discontinuity it may not be be
- * actual discontinuity if the head
- * of parent N happens to be the tail
- * of parent N+1.
- *
- * The next push onto the stack will
- * resolve the question.
- */
- (*stack)->item->object.flags |= DISCONTINUITY;
- }
- }
- }
- }
-
- push_onto_merge_order_stack(stack, head);
-}
-
-/*
- * Emit the contents of the stack.
- *
- * The stack is freed and replaced by NULL.
- *
- * Sets the return value to STOP if no further output should be generated.
- */
-static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last)
-{
- unsigned int seen = 0;
- int action = CONTINUE;
-
- while (*stack && (action != STOP)) {
- struct commit *next = pop_commit(stack);
- seen |= next->object.flags;
- if (*stack || include_last) {
- if (!*stack)
- next->object.flags |= BOUNDARY;
- action = emitter(next);
- }
- }
-
- if (*stack) {
- free_commit_list(*stack);
- *stack = NULL;
- }
-
- return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE;
-}
-
-/*
- * Sorts an arbitrary epoch into merge order by sorting each epoch
- * of its epoch sequence into order.
- *
- * Note: this algorithm currently leaves traces of its execution in the
- * object flags of nodes it discovers. This should probably be fixed.
- */
-static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter)
-{
- struct commit *next = head_of_epoch;
- int ret = 0;
- int action = CONTINUE;
-
- ret = parse_commit(head_of_epoch);
-
- next->object.flags |= BOUNDARY;
-
- while (next && next->parents && !ret && (action != STOP)) {
- struct commit *base = NULL;
-
- ret = find_next_epoch_boundary(next, &base);
- if (ret)
- return ret;
- next->object.flags |= BOUNDARY;
- if (base)
- base->object.flags |= BOUNDARY;
-
- if (HAS_EXACTLY_ONE_PARENT(next)) {
- while (HAS_EXACTLY_ONE_PARENT(next)
- && (action != STOP)
- && !ret) {
- if (next->object.flags & UNINTERESTING) {
- action = STOP;
- } else {
- action = emitter(next);
- }
- if (action != STOP) {
- next = next->parents->item;
- ret = parse_commit(next);
- }
- }
-
- } else {
- struct commit_list *stack = NULL;
- sort_first_epoch(next, &stack);
- action = emit_stack(&stack, emitter, (base == NULL));
- next = base;
- }
- }
-
- if (next && (action != STOP) && !ret) {
- emitter(next);
- }
-
- return ret;
-}
-
-/*
- * Sorts the nodes reachable from a starting list in merge order, we
- * first find the base for the starting list and then sort all nodes
- * in this subgraph using the sort_first_epoch algorithm. Once we have
- * reached the base we can continue sorting using sort_in_merge_order.
- */
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter)
-{
- struct commit_list *stack = NULL;
- struct commit *base;
- int ret = 0;
- int action = CONTINUE;
- struct commit_list *reversed = NULL;
-
- for (; list; list = list->next)
- commit_list_insert(list->item, &reversed);
-
- if (!reversed)
- return ret;
- else if (!reversed->next) {
- /*
- * If there is only one element in the list, we can sort it
- * using sort_in_merge_order.
- */
- base = reversed->item;
- } else {
- /*
- * Otherwise, we search for the base of the list.
- */
- ret = find_base_for_list(reversed, &base);
- if (ret)
- return ret;
- if (base)
- base->object.flags |= BOUNDARY;
-
- while (reversed) {
- struct commit * next = pop_commit(&reversed);
-
- if (!(next->object.flags & VISITED) && next!=base) {
- sort_first_epoch(next, &stack);
- if (reversed) {
- /*
- * If we have more commits
- * to push, then the first
- * push for the next parent may
- * (or may * not) represent a
- * discontinuity with respect
- * to the parent currently on
- * the top of the stack.
- *
- * Mark it for checking here,
- * and check it with the next
- * push. See sort_first_epoch()
- * for more details.
- */
- stack->item->object.flags |= DISCONTINUITY;
- }
- }
- }
-
- action = emit_stack(&stack, emitter, (base==NULL));
- }
-
- if (base && (action != STOP)) {
- ret = sort_in_merge_order(base, emitter);
- }
-
- return ret;
-}
diff --git a/epoch.h b/epoch.h
deleted file mode 100644
index 7493d5a241..0000000000
--- a/epoch.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef EPOCH_H
-#define EPOCH_H
-
-
-// return codes for emitter_func
-#define STOP 0
-#define CONTINUE 1
-#define DO 2
-typedef int (*emitter_func) (struct commit *);
-
-int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter);
-
-/* Low bits are used by rev-list */
-#define UNINTERESTING (1u<<10)
-#define BOUNDARY (1u<<11)
-#define VISITED (1u<<12)
-#define DISCONTINUITY (1u<<13)
-#define LAST_EPOCH_FLAG (1u<<14)
-
-
-#endif /* EPOCH_H */
diff --git a/git-am.sh b/git-am.sh
index 7cc4ae5a30..eab4aa891e 100755
--- a/git-am.sh
+++ b/git-am.sh
@@ -2,7 +2,8 @@
#
# Copyright (c) 2005, 2006 Junio C Hamano
-USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>
+USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way]
+ [--interactive] [--whitespace=<option>] <mbox>...
or, when resuming [--skip | --resolved]'
. git-sh-setup
@@ -100,7 +101,7 @@ fall_back_3way () {
}
prec=4
-dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary=
+dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws=
while case "$#" in 0) break;; esac
do
@@ -133,6 +134,9 @@ do
--sk|--ski|--skip)
skip=t; shift ;;
+ --whitespace=*)
+ ws=$1; shift ;;
+
--)
shift; break ;;
-*)
@@ -171,10 +175,11 @@ else
exit 1
}
- # -b, -s, -u and -k flags are kept for the resuming session after
- # a patch failure.
+ # -b, -s, -u, -k and --whitespace flags are kept for the
+ # resuming session after a patch failure.
# -3 and -i can and must be given when resuming.
echo "$binary" >"$dotest/binary"
+ echo " $ws" >"$dotest/whitespace"
echo "$sign" >"$dotest/sign"
echo "$utf8" >"$dotest/utf8"
echo "$keep" >"$dotest/keep"
@@ -202,6 +207,7 @@ if test "$(cat "$dotest/keep")" = t
then
keep=-k
fi
+ws=`cat "$dotest/whitespace"`
if test "$(cat "$dotest/sign")" = t
then
SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e '
@@ -355,7 +361,7 @@ do
case "$resolved" in
'')
- git-apply $binary --index "$dotest/patch"
+ git-apply $binary --index $ws "$dotest/patch"
apply_status=$?
;;
t)
diff --git a/git-annotate.perl b/git-annotate.perl
index f9c2c6caf5..d93ee19c7e 100755
--- a/git-annotate.perl
+++ b/git-annotate.perl
@@ -15,6 +15,8 @@ sub usage() {
print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ]
-l, --long
Show long rev (Defaults off)
+ -t, --time
+ Show raw timestamp (Defaults off)
-r, --rename
Follow renames (Defaults on).
-S, --rev-file revs-file
@@ -26,12 +28,13 @@ sub usage() {
exit(1);
}
-our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1);
+our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1);
my $rc = GetOptions( "long|l" => \$longrev,
+ "time|t" => \$rawtime,
"help|h" => \$help,
"rename|r" => \$rename,
- "rev-file|S" => \$rev_file);
+ "rev-file|S=s" => \$rev_file);
if (!$rc or $help) {
usage();
}
@@ -125,7 +128,7 @@ foreach my $l (@filelines) {
}
printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer,
- format_date($date), $i++, $output);
+ format_date($date), ++$i, $output);
}
sub init_claim {
@@ -174,7 +177,8 @@ sub git_rev_list {
my $revlist;
if ($rev_file) {
- open($revlist, '<' . $rev_file);
+ open($revlist, '<' . $rev_file)
+ or die "Failed to open $rev_file : $!";
} else {
$revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file)
or die "Failed to exec git-rev-list: $!";
@@ -304,6 +308,12 @@ sub _git_diff_parse {
}
$ri++;
+ } elsif (m/^\\/) {
+ ;
+ # Skip \No newline at end of file.
+ # But this can be internationalized, so only look
+ # for an initial \
+
} else {
if (substr($_,1) ne get_line($slines,$ri) ) {
die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n",
@@ -404,8 +414,10 @@ sub git_commit_info {
}
sub format_date {
+ if ($rawtime) {
+ return $_[0];
+ }
my ($timestamp, $timezone) = split(' ', $_[0]);
-
return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp));
}
diff --git a/git-archimport.perl b/git-archimport.perl
index 6792624d46..740bc1fd52 100755
--- a/git-archimport.perl
+++ b/git-archimport.perl
@@ -928,7 +928,7 @@ sub find_parents {
# now walk up to the mergepoint collecting what patches we have
my $branchtip = git_rev_parse($ps->{branch});
- my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`;
+ my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`;
my %have; # collected merges this branch has
foreach my $merge (@{$ps->{merges}}) {
$have{$merge} = 1;
@@ -951,7 +951,7 @@ sub find_parents {
# see what the remote branch has - these are the merges we
# will want to have in a consecutive series from the mergebase
my $otherbranchtip = git_rev_parse($branch);
- my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`;
+ my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`;
my @need;
foreach my $needps (@needraw) { # get the psets
$needps = commitid2pset($needps);
diff --git a/git-branch.sh b/git-branch.sh
index 6ac961e6d1..663a3a370c 100755
--- a/git-branch.sh
+++ b/git-branch.sh
@@ -48,6 +48,12 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'."
exit 0
}
+ls_remote_branches () {
+ git-rev-parse --symbolic --all |
+ sed -ne 's|^refs/\(remotes/\)|\1|p' |
+ sort
+}
+
force=
while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac
do
@@ -56,6 +62,10 @@ do
delete_branch "$@"
exit
;;
+ -r)
+ ls_remote_branches
+ exit
+ ;;
-f)
force="$1"
;;
diff --git a/git-commit.sh b/git-commit.sh
index f7ee1aadee..d9ec1f14d9 100755
--- a/git-commit.sh
+++ b/git-commit.sh
@@ -3,7 +3,7 @@
# Copyright (c) 2005 Linus Torvalds
# Copyright (c) 2006 Junio C Hamano
-USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [--author <author>] [[-i | -o] <path>...]'
+USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>) [--amend] [-e] [--author <author>] [[-i | -o] <path>...]'
SUBDIRECTORY_OK=Yes
. git-sh-setup
@@ -64,6 +64,22 @@ run_status () {
# We always show status for the whole tree.
cd "$TOP"
+ IS_INITIAL="$initial_commit"
+ REFERENCE=HEAD
+ case "$amend" in
+ t)
+ # If we are amending the initial commit, there
+ # is no HEAD^1.
+ if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1
+ then
+ REFERENCE="HEAD^1"
+ IS_INITIAL=
+ else
+ IS_INITIAL=t
+ fi
+ ;;
+ esac
+
# If TMP_INDEX is defined, that means we are doing
# "--only" partial commit, and that index file is used
# to build the tree for the commit. Otherwise, if
@@ -85,10 +101,10 @@ run_status () {
*) echo "# On branch $branch" ;;
esac
- if test -z "$initial_commit"
+ if test -z "$IS_INITIAL"
then
git-diff-index -M --cached --name-status \
- --diff-filter=MDTCRA HEAD |
+ --diff-filter=MDTCRA $REFERENCE |
sed -e '
s/\\/\\\\/g
s/ /\\ /g
@@ -147,7 +163,7 @@ run_status () {
if test -n "$verbose"
then
- git-diff-index --cached -M -p --diff-filter=MDTCRA HEAD
+ git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE
fi
case "$committable" in
0)
@@ -173,6 +189,7 @@ also=
only=
logfile=
use_commit=
+amend=
no_edit=
log_given=
log_message=
@@ -254,6 +271,12 @@ do
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
@@ -328,6 +351,15 @@ done
################################################################
# Sanity check options
+case "$amend,$initial_commit" in
+t,t)
+ 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 ;;
+esac
+
case "$log_given" in
tt*)
die "Only one of -c/-C/-F/-m can be used." ;;
@@ -559,13 +591,18 @@ if test -z "$initial_commit"
then
if [ -f "$GIT_DIR/MERGE_HEAD" ]; then
PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"`
+ elif test -n "$amend"; then
+ PARENTS=$(git-cat-file commit HEAD |
+ sed -n -e '/^$/q' -e 's/^parent /-p /p')
fi
+ current=$(git-rev-parse --verify HEAD)
else
if [ -z "$(git-ls-files)" ]; then
echo >&2 Nothing to commit
exit 1
fi
PARENTS=""
+ current=
fi
{
diff --git a/git-cvsserver.perl b/git-cvsserver.perl
index d20d1a8c4b..7d3f78e375 100755
--- a/git-cvsserver.perl
+++ b/git-cvsserver.perl
@@ -53,6 +53,7 @@ my $methods = {
'Entry' => \&req_Entry,
'Modified' => \&req_Modified,
'Unchanged' => \&req_Unchanged,
+ 'Questionable' => \&req_Questionable,
'Argument' => \&req_Argument,
'Argumentx' => \&req_Argument,
'expand-modules' => \&req_expandmodules,
@@ -63,6 +64,7 @@ my $methods = {
'ci' => \&req_ci,
'diff' => \&req_diff,
'log' => \&req_log,
+ 'rlog' => \&req_log,
'tag' => \&req_CATCHALL,
'status' => \&req_status,
'admin' => \&req_CATCHALL,
@@ -85,6 +87,31 @@ $log->info("--------------- STARTING -----------------");
my $TEMP_DIR = tempdir( CLEANUP => 1 );
$log->debug("Temporary directory is '$TEMP_DIR'");
+# if we are called with a pserver argument,
+# deal with the authentication cat before entereing the
+# main loop
+if (@ARGV && $ARGV[0] eq 'pserver') {
+ my $line = <STDIN>; chomp $line;
+ unless( $line eq 'BEGIN AUTH REQUEST') {
+ die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n";
+ }
+ $line = <STDIN>; chomp $line;
+ req_Root('root', $line) # reuse Root
+ or die "E Invalid root $line \n";
+ $line = <STDIN>; chomp $line;
+ unless ($line eq 'anonymous') {
+ print "E Only anonymous user allowed via pserver\n";
+ print "I HATE YOU\n";
+ }
+ $line = <STDIN>; chomp $line; # validate the password?
+ $line = <STDIN>; chomp $line;
+ unless ($line eq 'END AUTH REQUEST') {
+ die "E Do not understand $line -- expecting END AUTH REQUEST\n";
+ }
+ print "I LOVE YOU\n";
+ # and now back to our regular programme...
+}
+
# Keep going until the client closes the connection
while (<STDIN>)
{
@@ -137,8 +164,21 @@ sub req_Root
$state->{CVSROOT} = $data;
$ENV{GIT_DIR} = $state->{CVSROOT} . "/";
+ unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') {
+ print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n";
+ print "E \n";
+ print "error 1 $ENV{GIT_DIR} is not a valid repository\n";
+ return 0;
+ }
- foreach my $line ( `git-var -l` )
+ my @gitvars = `git-var -l`;
+ if ($?) {
+ print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n";
+ print "E \n";
+ print "error 1 - problem executing git-var\n";
+ return 0;
+ }
+ foreach my $line ( @gitvars )
{
next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ );
$cfg->{$1}{$2} = $3;
@@ -150,6 +190,7 @@ sub req_Root
print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n";
print "E \n";
print "error 1 GITCVS emulation disabled\n";
+ return 0;
}
if ( defined ( $cfg->{gitcvs}{logfile} ) )
@@ -158,6 +199,8 @@ sub req_Root
} else {
$log->nofile();
}
+
+ return 1;
}
# Global_option option \n
@@ -459,6 +502,22 @@ sub req_Unchanged
#$log->debug("req_Unchanged : $data");
}
+# Questionable filename \n
+# Response expected: no. Additional data: no.
+# Tell the server to check whether filename should be ignored,
+# and if not, next time the server sends responses, send (in
+# a M response) `?' followed by the directory and filename.
+# filename must not contain `/'; it needs to be a file in the
+# directory named by the most recent Directory request.
+sub req_Questionable
+{
+ my ( $cmd, $data ) = @_;
+
+ $state->{entries}{$state->{directory}.$data}{questionable} = 1;
+
+ #$log->debug("req_Questionable : $data");
+}
+
# Argument text \n
# Response expected: no. Save argument for use in a subsequent command.
# Arguments accumulate until an argument-using command is given, at which
@@ -553,8 +612,62 @@ sub req_co
my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log);
$updater->update();
+ $checkout_path =~ s|/$||; # get rid of trailing slashes
+
+ # Eclipse seems to need the Clear-sticky command
+ # to prepare the 'Entries' file for the new directory.
+ print "Clear-sticky $checkout_path/\n";
+ print $state->{CVSROOT} . "/$module/\n";
+ print "Clear-static-directory $checkout_path/\n";
+ print $state->{CVSROOT} . "/$module/\n";
+ print "Clear-sticky $checkout_path/\n"; # yes, twice
+ print $state->{CVSROOT} . "/$module/\n";
+ print "Template $checkout_path/\n";
+ print $state->{CVSROOT} . "/$module/\n";
+ print "0\n";
+
# instruct the client that we're checking out to $checkout_path
- print "E cvs server: updating $checkout_path\n";
+ print "E cvs checkout: Updating $checkout_path\n";
+
+ my %seendirs = ();
+ my $lastdir ='';
+
+ # recursive
+ sub prepdir {
+ my ($dir, $repodir, $remotedir, $seendirs) = @_;
+ my $parent = dirname($dir);
+ $dir =~ s|/+$||;
+ $repodir =~ s|/+$||;
+ $remotedir =~ s|/+$||;
+ $parent =~ s|/+$||;
+ $log->debug("announcedir $dir, $repodir, $remotedir" );
+
+ if ($parent eq '.' || $parent eq './') {
+ $parent = '';
+ }
+ # recurse to announce unseen parents first
+ if (length($parent) && !exists($seendirs->{$parent})) {
+ prepdir($parent, $repodir, $remotedir, $seendirs);
+ }
+ # Announce that we are going to modify at the parent level
+ if ($parent) {
+ print "E cvs checkout: Updating $remotedir/$parent\n";
+ } else {
+ print "E cvs checkout: Updating $remotedir\n";
+ }
+ print "Clear-sticky $remotedir/$parent/\n";
+ print "$repodir/$parent/\n";
+
+ print "Clear-static-directory $remotedir/$dir/\n";
+ print "$repodir/$dir/\n";
+ print "Clear-sticky $remotedir/$parent/\n"; # yes, twice
+ print "$repodir/$parent/\n";
+ print "Template $remotedir/$dir/\n";
+ print "$repodir/$dir/\n";
+ print "0\n";
+
+ $seendirs->{$dir} = 1;
+ }
foreach my $git ( @{$updater->gethead} )
{
@@ -563,25 +676,32 @@ sub req_co
( $git->{name}, $git->{dir} ) = filenamesplit($git->{name});
+ if (length($git->{dir}) && $git->{dir} ne './'
+ && $git->{dir} ne $lastdir ) {
+ unless (exists($seendirs{$git->{dir}})) {
+ prepdir($git->{dir}, $state->{CVSROOT} . "/$module/",
+ $checkout_path, \%seendirs);
+ $lastdir = $git->{dir};
+ $seendirs{$git->{dir}} = 1;
+ }
+ print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n";
+ }
+
# modification time of this file
print "Mod-time $git->{modified}\n";
# print some information to the client
- print "MT +updated\n";
- print "MT text U\n";
if ( defined ( $git->{dir} ) and $git->{dir} ne "./" )
{
- print "MT fname $checkout_path/$git->{dir}$git->{name}\n";
+ print "M U $checkout_path/$git->{dir}$git->{name}\n";
} else {
- print "MT fname $checkout_path/$git->{name}\n";
+ print "M U $checkout_path/$git->{name}\n";
}
- print "MT newline\n";
- print "MT -updated\n";
- # instruct client we're sending a file to put in this path
- print "Created $checkout_path/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "\n";
+ # instruct client we're sending a file to put in this path
+ print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n";
- print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
+ print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n";
# this is an "entries" line
print "/$git->{name}/1.$git->{revision}///\n";
@@ -612,6 +732,26 @@ sub req_update
argsplit("update");
+ #
+ # It may just be a client exploring the available heads/modukles
+ # in that case, list them as top level directories and leave it
+ # at that. Eclipse uses this technique to offer you a list of
+ # projects (heads in this case) to checkout.
+ #
+ if ($state->{module} eq '') {
+ print "E cvs update: Updating .\n";
+ opendir HEADS, $state->{CVSROOT} . '/refs/heads';
+ while (my $head = readdir(HEADS)) {
+ if (-f $state->{CVSROOT} . '/refs/heads/' . $head) {
+ print "E cvs update: New directory `$head'\n";
+ }
+ }
+ closedir HEADS;
+ print "ok\n";
+ return 1;
+ }
+
+
# Grab a handle to the SQLite db and do any necessary updates
my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);
@@ -657,8 +797,27 @@ sub req_update
#$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");
- # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified _and_ the user hasn't specified -C
- next if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{unchanged} and not exists ( $state->{opt}{C} ) );
+ # Files are up to date if the working copy and repo copy have the same revision,
+ # and the working copy is unmodified _and_ the user hasn't specified -C
+ next if ( defined ( $wrev )
+ and defined($meta->{revision})
+ and $wrev == $meta->{revision}
+ and $state->{entries}{$filename}{unchanged}
+ and not exists ( $state->{opt}{C} ) );
+
+ # If the working copy and repo copy have the same revision,
+ # but the working copy is modified, tell the client it's modified
+ if ( defined ( $wrev )
+ and defined($meta->{revision})
+ and $wrev == $meta->{revision}
+ and not exists ( $state->{opt}{C} ) )
+ {
+ $log->info("Tell the client the file is modified");
+ print "MT text U\n";
+ print "MT fname $filename\n";
+ print "MT newline\n";
+ next;
+ }
if ( $meta->{filehash} eq "deleted" )
{
@@ -670,7 +829,8 @@ sub req_update
print "Removed $dirpart\n";
print "$filepart\n";
}
- elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
+ elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
+ or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} )
{
$log->info("Updating '$filename'");
# normal update, just send the new revision (either U=Update, or A=Add, or R=Remove)
@@ -706,6 +866,7 @@ sub req_update
# transmit file
transmitfile($meta->{filehash});
} else {
+ $log->info("Updating '$filename'");
my ( $filepart, $dirpart ) = filenamesplit($meta->{name});
my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/";
@@ -781,6 +942,12 @@ sub req_ci
$log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" ));
+ if ( @ARGV && $ARGV[0] eq 'pserver')
+ {
+ print "error 1 pserver access cannot commit\n";
+ exit;
+ }
+
if ( -e $state->{CVSROOT} . "/index" )
{
print "error 1 Index already exists in git repo\n";
@@ -2271,7 +2438,7 @@ sub gethead
return $self->{gethead_cache} if ( defined ( $self->{gethead_cache} ) );
- my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head",{},1);
+ my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head ORDER BY name ASC",{},1);
$db_query->execute();
my $tree = [];
diff --git a/git-mv.perl b/git-mv.perl
index 2ea852c918..75aa8feeb6 100755
--- a/git-mv.perl
+++ b/git-mv.perl
@@ -19,25 +19,26 @@ EOT
exit(1);
}
-my $GIT_DIR = `git rev-parse --git-dir`;
-exit 1 if $?; # rev-parse would have given "not a git dir" message.
-chomp($GIT_DIR);
-
our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v);
getopts("hnfkv") || usage;
usage() if $opt_h;
@ARGV >= 1 or usage;
+my $GIT_DIR = `git rev-parse --git-dir`;
+exit 1 if $?; # rev-parse would have given "not a git dir" message.
+chomp($GIT_DIR);
+
my (@srcArgs, @dstArgs, @srcs, @dsts);
my ($src, $dst, $base, $dstDir);
+# remove any trailing slash in arguments
+for (@ARGV) { s/\/*$//; }
+
my $argCount = scalar @ARGV;
if (-d $ARGV[$argCount-1]) {
$dstDir = $ARGV[$argCount-1];
- # remove any trailing slash
- $dstDir =~ s/\/$//;
@srcArgs = @ARGV[0..$argCount-2];
-
+
foreach $src (@srcArgs) {
$base = $src;
$base =~ s/^.*\///;
@@ -46,10 +47,14 @@ if (-d $ARGV[$argCount-1]) {
}
}
else {
- if ($argCount != 2) {
+ if ($argCount < 2) {
+ print "Error: need at least two arguments\n";
+ exit(1);
+ }
+ if ($argCount > 2) {
print "Error: moving to directory '"
. $ARGV[$argCount-1]
- . "' not possible; not exisiting\n";
+ . "' not possible; not existing\n";
exit(1);
}
@srcArgs = ($ARGV[0]);
@@ -57,6 +62,24 @@ else {
$dstDir = "";
}
+my $subdir_prefix = `git rev-parse --show-prefix`;
+chomp($subdir_prefix);
+
+# run in git base directory, so that git-ls-files lists all revisioned files
+chdir "$GIT_DIR/..";
+
+# normalize paths, needed to compare against versioned files and update-index
+# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c"
+for (@srcArgs, @dstArgs) {
+ # prepend git prefix as we run from base directory
+ $_ = $subdir_prefix.$_;
+ s|^\./||;
+ s|/\./|/| while (m|/\./|);
+ s|//+|/|g;
+ # Also "a/b/../c" ==> "a/c"
+ 1 while (s,(^|/)[^/]+/\.\./,$1,);
+}
+
my (@allfiles,@srcfiles,@dstfiles);
my $safesrc;
my (%overwritten, %srcForDst);
diff --git a/git-send-email.perl b/git-send-email.perl
index b0d095b4e9..7c8d51223f 100755
--- a/git-send-email.perl
+++ b/git-send-email.perl
@@ -54,7 +54,7 @@ my $rc = GetOptions("from=s" => \$from,
"compose" => \$compose,
"quiet" => \$quiet,
"suppress-from" => \$suppress_from,
- "no-signed-off-cc" => \$no_signed_off_cc,
+ "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc,
);
# Now, let's fill any that aren't set in with defaults:
diff --git a/git-svnimport.perl b/git-svnimport.perl
index 86837edbdd..639aa41861 100755
--- a/git-svnimport.perl
+++ b/git-svnimport.perl
@@ -13,6 +13,7 @@
use strict;
use warnings;
use Getopt::Std;
+use File::Copy;
use File::Spec;
use File::Temp qw(tempfile);
use File::Path qw(mkpath);
@@ -68,10 +69,16 @@ if ($opt_M) {
push (@mergerx, qr/$opt_M/);
}
+# Absolutize filename now, since we will have chdir'ed by the time we
+# get around to opening it.
+$opt_A = File::Spec->rel2abs($opt_A) if $opt_A;
+
our %users = ();
-if ($opt_A) {
- die "Cannot open $opt_A\n" unless -f $opt_A;
- open(my $authors,$opt_A);
+our $users_file = undef;
+sub read_users($) {
+ $users_file = File::Spec->rel2abs(@_);
+ die "Cannot open $users_file\n" unless -f $users_file;
+ open(my $authors,$users_file);
while(<$authors>) {
chomp;
next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/;
@@ -302,6 +309,14 @@ EOM
-d $git_dir
or die "Could not create git subdir ($git_dir).\n";
+my $default_authors = "$git_dir/svn-authors";
+if ($opt_A) {
+ read_users($opt_A);
+ copy($opt_A,$default_authors) or die "Copy failed: $!";
+} else {
+ read_users($default_authors) if -f $default_authors;
+}
+
open BRANCHES,">>", "$git_dir/svn2git";
sub node_kind($$$) {
@@ -498,8 +513,8 @@ sub commit {
if (not defined $author) {
$author_name = $author_email = "unknown";
- } elsif ($opt_A) {
- die "User $author is not listed in $opt_A\n"
+ } elsif (defined $users_file) {
+ die "User $author is not listed in $users_file\n"
unless exists $users{$author};
($author_name,$author_email) = @{$users{$author}};
} elsif ($author =~ /^(.*?)\s+<(.*)>$/) {
diff --git a/git-verify-tag.sh b/git-verify-tag.sh
index 726b1e706b..36f171b302 100755
--- a/git-verify-tag.sh
+++ b/git-verify-tag.sh
@@ -4,9 +4,21 @@ USAGE='<tag>'
SUBDIRECTORY_OK='Yes'
. git-sh-setup
+verbose=
+while case $# in 0) break;; esac
+do
+ case "$1" in
+ -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
+ verbose=t ;;
+ *)
+ break ;;
+ esac
+ shift
+done
+
if [ "$#" != "1" ]
then
- usage
+ usage
fi
type="$(git-cat-file -t "$1" 2>/dev/null)" ||
@@ -15,6 +27,13 @@ type="$(git-cat-file -t "$1" 2>/dev/null)" ||
test "$type" = tag ||
die "$1: cannot verify a non-tag object of type $type."
+case "$verbose" in
+t)
+ git-cat-file -p "$1" |
+ sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p
+ ;;
+esac
+
git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1
cat "$GIT_DIR/.tmp-vtag" |
sed '/-----BEGIN PGP/Q' |
diff --git a/git.c b/git.c
index 993cd0d490..a547dbd913 100644
--- a/git.c
+++ b/git.c
@@ -12,6 +12,10 @@
#include "git-compat-util.h"
#include "exec_cmd.h"
+#include "cache.h"
+#include "commit.h"
+#include "revision.h"
+
#ifndef PATH_MAX
# define PATH_MAX 4096
#endif
@@ -245,6 +249,80 @@ static int cmd_help(int argc, char **argv, char **envp)
return 0;
}
+#define LOGSIZE (65536)
+
+static int cmd_log(int argc, char **argv, char **envp)
+{
+ struct rev_info rev;
+ struct commit *commit;
+ char *buf = xmalloc(LOGSIZE);
+ static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT;
+ int abbrev = DEFAULT_ABBREV;
+ int show_parents = 0;
+ const char *commit_prefix = "commit ";
+
+ argc = setup_revisions(argc, argv, &rev, "HEAD");
+ while (1 < argc) {
+ char *arg = argv[1];
+ if (!strncmp(arg, "--pretty", 8)) {
+ commit_format = get_commit_format(arg + 8);
+ if (commit_format == CMIT_FMT_ONELINE)
+ commit_prefix = "";
+ }
+ else if (!strcmp(arg, "--parents")) {
+ show_parents = 1;
+ }
+ else if (!strcmp(arg, "--no-abbrev")) {
+ abbrev = 0;
+ }
+ else if (!strncmp(arg, "--abbrev=", 9)) {
+ abbrev = strtoul(arg + 9, NULL, 10);
+ if (abbrev && abbrev < MINIMUM_ABBREV)
+ abbrev = MINIMUM_ABBREV;
+ else if (40 < abbrev)
+ abbrev = 40;
+ }
+ else
+ die("unrecognized argument: %s", arg);
+ argc--; argv++;
+ }
+
+ prepare_revision_walk(&rev);
+ setup_pager();
+ while ((commit = get_revision(&rev)) != NULL) {
+ printf("%s%s", commit_prefix,
+ sha1_to_hex(commit->object.sha1));
+ if (show_parents) {
+ struct commit_list *parents = commit->parents;
+ while (parents) {
+ struct object *o = &(parents->item->object);
+ parents = parents->next;
+ if (o->flags & TMP_MARK)
+ continue;
+ printf(" %s", sha1_to_hex(o->sha1));
+ o->flags |= TMP_MARK;
+ }
+ /* TMP_MARK is a general purpose flag that can
+ * be used locally, but the user should clean
+ * things up after it is done with them.
+ */
+ for (parents = commit->parents;
+ parents;
+ parents = parents->next)
+ parents->item->object.flags &= ~TMP_MARK;
+ }
+ if (commit_format == CMIT_FMT_ONELINE)
+ putchar(' ');
+ else
+ putchar('\n');
+ pretty_print_commit(commit_format, commit, ~0, buf,
+ LOGSIZE, abbrev);
+ printf("%s\n", buf);
+ }
+ free(buf);
+ return 0;
+}
+
#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
static void handle_internal_command(int argc, char **argv, char **envp)
@@ -256,6 +334,7 @@ static void handle_internal_command(int argc, char **argv, char **envp)
} commands[] = {
{ "version", cmd_version },
{ "help", cmd_help },
+ { "log", cmd_log },
};
int i;
diff --git a/pack-objects.c b/pack-objects.c
index 21ee572f48..136a7f5aad 100644
--- a/pack-objects.c
+++ b/pack-objects.c
@@ -99,7 +99,7 @@ static int reused_delta = 0;
static int pack_revindex_ix(struct packed_git *p)
{
- unsigned long ui = (unsigned long)(long)p;
+ unsigned long ui = (unsigned long)p;
int i;
ui = ui ^ (ui >> 16); /* defeat structure alignment */
diff --git a/pager.c b/pager.c
new file mode 100644
index 0000000000..1364e15d23
--- /dev/null
+++ b/pager.c
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+/*
+ * This is split up from the rest of git so that we might do
+ * something different on Windows, for example.
+ */
+
+static void run_pager(void)
+{
+ const char *prog = getenv("PAGER");
+ if (!prog)
+ prog = "less";
+ setenv("LESS", "-S", 0);
+ execlp(prog, prog, NULL);
+}
+
+void setup_pager(void)
+{
+ pid_t pid;
+ int fd[2];
+
+ if (!isatty(1))
+ return;
+ if (pipe(fd) < 0)
+ return;
+ pid = fork();
+ if (pid < 0) {
+ close(fd[0]);
+ close(fd[1]);
+ return;
+ }
+
+ /* return in the child */
+ if (!pid) {
+ dup2(fd[1], 1);
+ close(fd[0]);
+ close(fd[1]);
+ return;
+ }
+
+ /* The original process turns into the PAGER */
+ dup2(fd[0], 0);
+ close(fd[0]);
+ close(fd[1]);
+
+ run_pager();
+ exit(255);
+}
diff --git a/read-tree.c b/read-tree.c
index f39fe5ca65..be29b3fe11 100644
--- a/read-tree.c
+++ b/read-tree.c
@@ -560,9 +560,11 @@ static int threeway_merge(struct cache_entry **stages)
*/
if ((head_deleted && remote_deleted) ||
(head_deleted && remote && remote_match) ||
- (remote_deleted && head && head_match))
+ (remote_deleted && head && head_match)) {
+ if (index)
+ return deleted_entry(index, index);
return 0;
-
+ }
/*
* Added in both, identically.
*/
@@ -704,7 +706,7 @@ static int read_cache_unmerged(void)
return deleted;
}
-static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u | -i] <sha1> [<sha2> [<sha3>]])";
+static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])";
static struct cache_file cache_file;
diff --git a/refs.c b/refs.c
index 826ae7ade7..982ebf8ae5 100644
--- a/refs.c
+++ b/refs.c
@@ -151,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u
break;
continue;
}
- if (read_ref(git_path("%s", path), sha1) < 0)
+ if (read_ref(git_path("%s", path), sha1) < 0) {
+ fprintf(stderr, "%s points nowhere!", path);
continue;
- if (!has_sha1_file(sha1))
+ }
+ if (!has_sha1_file(sha1)) {
+ fprintf(stderr, "%s does not point to a valid "
+ "commit object!", path);
continue;
+ }
retval = fn(path, sha1);
if (retval)
break;
diff --git a/rev-list.c b/rev-list.c
index 67d2a483fc..8e4d83efba 100644
--- a/rev-list.c
+++ b/rev-list.c
@@ -4,15 +4,12 @@
#include "commit.h"
#include "tree.h"
#include "blob.h"
-#include "epoch.h"
#include "diff.h"
+#include "revision.h"
-#define SEEN (1u << 0)
-#define INTERESTING (1u << 1)
-#define COUNTED (1u << 2)
-#define SHOWN (1u << 3)
-#define TREECHANGE (1u << 4)
-#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */
+/* bits #0-4 in revision.h */
+
+#define COUNTED (1u<<5)
static const char rev_list_usage[] =
"git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -25,7 +22,6 @@ static const char rev_list_usage[] =
" --remove-empty\n"
" --all\n"
" ordering output:\n"
-" --merge-order [ --show-breaks ]\n"
" --topo-order\n"
" --date-order\n"
" formatting output:\n"
@@ -38,72 +34,18 @@ static const char rev_list_usage[] =
" --bisect"
;
-static int dense = 1;
-static int unpacked = 0;
+struct rev_info revs;
+
static int bisect_list = 0;
-static int tag_objects = 0;
-static int tree_objects = 0;
-static int blob_objects = 0;
-static int edge_hint = 0;
static int verbose_header = 0;
static int abbrev = DEFAULT_ABBREV;
static int show_parents = 0;
static int hdr_termination = 0;
static const char *commit_prefix = "";
-static unsigned long max_age = -1;
-static unsigned long min_age = -1;
-static int max_count = -1;
static enum cmit_fmt commit_format = CMIT_FMT_RAW;
-static int merge_order = 0;
-static int show_breaks = 0;
-static int stop_traversal = 0;
-static int topo_order = 0;
-static int lifo = 1;
-static int no_merges = 0;
-static const char **paths = NULL;
-static int remove_empty_trees = 0;
-
-struct name_path {
- struct name_path *up;
- int elem_len;
- const char *elem;
-};
-
-static char *path_name(struct name_path *path, const char *name)
-{
- struct name_path *p;
- char *n, *m;
- int nlen = strlen(name);
- int len = nlen + 1;
-
- for (p = path; p; p = p->up) {
- if (p->elem_len)
- len += p->elem_len + 1;
- }
- n = xmalloc(len);
- m = n + len - (nlen + 1);
- strcpy(m, name);
- for (p = path; p; p = p->up) {
- if (p->elem_len) {
- m -= p->elem_len + 1;
- memcpy(m, p->elem, p->elem_len);
- m[p->elem_len] = '/';
- }
- }
- return n;
-}
static void show_commit(struct commit *commit)
{
- commit->object.flags |= SHOWN;
- if (show_breaks) {
- commit_prefix = "| ";
- if (commit->object.flags & DISCONTINUITY) {
- commit_prefix = "^ ";
- } else if (commit->object.flags & BOUNDARY) {
- commit_prefix = "= ";
- }
- }
printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1));
if (show_parents) {
struct commit_list *parents = commit->parents;
@@ -137,86 +79,6 @@ static void show_commit(struct commit *commit)
fflush(stdout);
}
-static int rewrite_one(struct commit **pp)
-{
- for (;;) {
- struct commit *p = *pp;
- if (p->object.flags & (TREECHANGE | UNINTERESTING))
- return 0;
- if (!p->parents)
- return -1;
- *pp = p->parents->item;
- }
-}
-
-static void rewrite_parents(struct commit *commit)
-{
- struct commit_list **pp = &commit->parents;
- while (*pp) {
- struct commit_list *parent = *pp;
- if (rewrite_one(&parent->item) < 0) {
- *pp = parent->next;
- continue;
- }
- pp = &parent->next;
- }
-}
-
-static int filter_commit(struct commit * commit)
-{
- if (stop_traversal && (commit->object.flags & BOUNDARY))
- return STOP;
- if (commit->object.flags & (UNINTERESTING|SHOWN))
- return CONTINUE;
- if (min_age != -1 && (commit->date > min_age))
- return CONTINUE;
- if (max_age != -1 && (commit->date < max_age)) {
- stop_traversal=1;
- return CONTINUE;
- }
- if (no_merges && (commit->parents && commit->parents->next))
- return CONTINUE;
- if (paths && dense) {
- if (!(commit->object.flags & TREECHANGE))
- return CONTINUE;
- rewrite_parents(commit);
- }
- return DO;
-}
-
-static int process_commit(struct commit * commit)
-{
- int action=filter_commit(commit);
-
- if (action == STOP) {
- return STOP;
- }
-
- if (action == CONTINUE) {
- return CONTINUE;
- }
-
- if (max_count != -1 && !max_count--)
- return STOP;
-
- show_commit(commit);
-
- return CONTINUE;
-}
-
-static struct object_list **add_object(struct object *obj,
- struct object_list **p,
- struct name_path *path,
- const char *name)
-{
- struct object_list *entry = xmalloc(sizeof(*entry));
- entry->item = obj;
- entry->next = *p;
- entry->name = path_name(path, name);
- *p = entry;
- return &entry->next;
-}
-
static struct object_list **process_blob(struct blob *blob,
struct object_list **p,
struct name_path *path,
@@ -224,7 +86,7 @@ static struct object_list **process_blob(struct blob *blob,
{
struct object *obj = &blob->object;
- if (!blob_objects)
+ if (!revs.blob_objects)
return p;
if (obj->flags & (UNINTERESTING | SEEN))
return p;
@@ -241,7 +103,7 @@ static struct object_list **process_tree(struct tree *tree,
struct tree_entry_list *entry;
struct name_path me;
- if (!tree_objects)
+ if (!revs.tree_objects)
return p;
if (obj->flags & (UNINTERESTING | SEEN))
return p;
@@ -266,19 +128,16 @@ static struct object_list **process_tree(struct tree *tree,
return p;
}
-static struct object_list *pending_objects = NULL;
-
-static void show_commit_list(struct commit_list *list)
+static void show_commit_list(struct rev_info *revs)
{
+ struct commit *commit;
struct object_list *objects = NULL, **p = &objects, *pending;
- while (list) {
- struct commit *commit = pop_most_recent_commit(&list, SEEN);
+ while ((commit = get_revision(revs)) != NULL) {
p = process_tree(commit->tree, p, NULL, "");
- if (process_commit(commit) == STOP)
- break;
+ show_commit(commit);
}
- for (pending = pending_objects; pending; pending = pending->next) {
+ for (pending = revs->pending_objects; pending; pending = pending->next) {
struct object *obj = pending->item;
const char *name = pending->name;
if (obj->flags & (UNINTERESTING | SEEN))
@@ -314,88 +173,6 @@ static void show_commit_list(struct commit_list *list)
}
}
-static void mark_blob_uninteresting(struct blob *blob)
-{
- if (!blob_objects)
- return;
- if (blob->object.flags & UNINTERESTING)
- return;
- blob->object.flags |= UNINTERESTING;
-}
-
-static void mark_tree_uninteresting(struct tree *tree)
-{
- struct object *obj = &tree->object;
- struct tree_entry_list *entry;
-
- if (!tree_objects)
- return;
- if (obj->flags & UNINTERESTING)
- return;
- obj->flags |= UNINTERESTING;
- if (!has_sha1_file(obj->sha1))
- return;
- if (parse_tree(tree) < 0)
- die("bad tree %s", sha1_to_hex(obj->sha1));
- entry = tree->entries;
- tree->entries = NULL;
- while (entry) {
- struct tree_entry_list *next = entry->next;
- if (entry->directory)
- mark_tree_uninteresting(entry->item.tree);
- else
- mark_blob_uninteresting(entry->item.blob);
- free(entry);
- entry = next;
- }
-}
-
-static void mark_parents_uninteresting(struct commit *commit)
-{
- struct commit_list *parents = commit->parents;
-
- while (parents) {
- struct commit *commit = parents->item;
- commit->object.flags |= UNINTERESTING;
-
- /*
- * Normally we haven't parsed the parent
- * yet, so we won't have a parent of a parent
- * here. However, it may turn out that we've
- * reached this commit some other way (where it
- * wasn't uninteresting), in which case we need
- * to mark its parents recursively too..
- */
- if (commit->parents)
- mark_parents_uninteresting(commit);
-
- /*
- * A missing commit is ok iff its parent is marked
- * uninteresting.
- *
- * We just mark such a thing parsed, so that when
- * it is popped next time around, we won't be trying
- * to parse it and get an error.
- */
- if (!has_sha1_file(commit->object.sha1))
- commit->object.parsed = 1;
- parents = parents->next;
- }
-}
-
-static int everybody_uninteresting(struct commit_list *orig)
-{
- struct commit_list *list = orig;
- while (list) {
- struct commit *commit = list->item;
- list = list->next;
- if (commit->object.flags & UNINTERESTING)
- continue;
- return 0;
- }
- return 1;
-}
-
/*
* This is a truly stupid algorithm, but it's only
* used for bisection, and we just don't care enough.
@@ -413,7 +190,7 @@ static int count_distance(struct commit_list *entry)
if (commit->object.flags & (UNINTERESTING | COUNTED))
break;
- if (!paths || (commit->object.flags & TREECHANGE))
+ if (!revs.paths || (commit->object.flags & TREECHANGE))
nr++;
commit->object.flags |= COUNTED;
p = commit->parents;
@@ -447,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
nr = 0;
p = list;
while (p) {
- if (!paths || (p->item->object.flags & TREECHANGE))
+ if (!revs.paths || (p->item->object.flags & TREECHANGE))
nr++;
p = p->next;
}
@@ -457,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list)
for (p = list; p; p = p->next) {
int distance;
- if (paths && !(p->item->object.flags & TREECHANGE))
+ if (revs.paths && !(p->item->object.flags & TREECHANGE))
continue;
distance = count_distance(p);
@@ -483,7 +260,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit)
if (!(parent->object.flags & UNINTERESTING))
continue;
mark_tree_uninteresting(parent->tree);
- if (edge_hint && !(parent->object.flags & SHOWN)) {
+ if (revs.edge_hint && !(parent->object.flags & SHOWN)) {
parent->object.flags |= SHOWN;
printf("-%s\n", sha1_to_hex(parent->object.sha1));
}
@@ -503,356 +280,29 @@ static void mark_edges_uninteresting(struct commit_list *list)
}
}
-#define TREE_SAME 0
-#define TREE_NEW 1
-#define TREE_DIFFERENT 2
-static int tree_difference = TREE_SAME;
-
-static void file_add_remove(struct diff_options *options,
- int addremove, unsigned mode,
- const unsigned char *sha1,
- const char *base, const char *path)
-{
- int diff = TREE_DIFFERENT;
-
- /*
- * Is it an add of a new file? It means that
- * the old tree didn't have it at all, so we
- * will turn "TREE_SAME" -> "TREE_NEW", but
- * leave any "TREE_DIFFERENT" alone (and if
- * it already was "TREE_NEW", we'll keep it
- * "TREE_NEW" of course).
- */
- if (addremove == '+') {
- diff = tree_difference;
- if (diff != TREE_SAME)
- return;
- diff = TREE_NEW;
- }
- tree_difference = diff;
-}
-
-static void file_change(struct diff_options *options,
- unsigned old_mode, unsigned new_mode,
- const unsigned char *old_sha1,
- const unsigned char *new_sha1,
- const char *base, const char *path)
-{
- tree_difference = TREE_DIFFERENT;
-}
-
-static struct diff_options diff_opt = {
- .recursive = 1,
- .add_remove = file_add_remove,
- .change = file_change,
-};
-
-static int compare_tree(struct tree *t1, struct tree *t2)
-{
- if (!t1)
- return TREE_NEW;
- if (!t2)
- return TREE_DIFFERENT;
- tree_difference = TREE_SAME;
- if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
- return TREE_DIFFERENT;
- return tree_difference;
-}
-
-static int same_tree_as_empty(struct tree *t1)
-{
- int retval;
- void *tree;
- struct tree_desc empty, real;
-
- if (!t1)
- return 0;
-
- tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
- if (!tree)
- return 0;
- real.buf = tree;
-
- empty.buf = "";
- empty.size = 0;
-
- tree_difference = 0;
- retval = diff_tree(&empty, &real, "", &diff_opt);
- free(tree);
-
- return retval >= 0 && !tree_difference;
-}
-
-static void try_to_simplify_commit(struct commit *commit)
-{
- struct commit_list **pp, *parent;
-
- if (!commit->tree)
- return;
-
- if (!commit->parents) {
- if (!same_tree_as_empty(commit->tree))
- commit->object.flags |= TREECHANGE;
- return;
- }
-
- pp = &commit->parents;
- while ((parent = *pp) != NULL) {
- struct commit *p = parent->item;
-
- if (p->object.flags & UNINTERESTING) {
- pp = &parent->next;
- continue;
- }
-
- parse_commit(p);
- switch (compare_tree(p->tree, commit->tree)) {
- case TREE_SAME:
- parent->next = NULL;
- commit->parents = parent;
- return;
-
- case TREE_NEW:
- if (remove_empty_trees && same_tree_as_empty(p->tree)) {
- *pp = parent->next;
- continue;
- }
- /* fallthrough */
- case TREE_DIFFERENT:
- pp = &parent->next;
- continue;
- }
- die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
- }
- commit->object.flags |= TREECHANGE;
-}
-
-static void add_parents_to_list(struct commit *commit, struct commit_list **list)
-{
- struct commit_list *parent = commit->parents;
-
- /*
- * If the commit is uninteresting, don't try to
- * prune parents - we want the maximal uninteresting
- * set.
- *
- * Normally we haven't parsed the parent
- * yet, so we won't have a parent of a parent
- * here. However, it may turn out that we've
- * reached this commit some other way (where it
- * wasn't uninteresting), in which case we need
- * to mark its parents recursively too..
- */
- if (commit->object.flags & UNINTERESTING) {
- while (parent) {
- struct commit *p = parent->item;
- parent = parent->next;
- parse_commit(p);
- p->object.flags |= UNINTERESTING;
- if (p->parents)
- mark_parents_uninteresting(p);
- if (p->object.flags & SEEN)
- continue;
- p->object.flags |= SEEN;
- insert_by_date(p, list);
- }
- return;
- }
-
- /*
- * Ok, the commit wasn't uninteresting. Try to
- * simplify the commit history and find the parent
- * that has no differences in the path set if one exists.
- */
- if (paths)
- try_to_simplify_commit(commit);
-
- parent = commit->parents;
- while (parent) {
- struct commit *p = parent->item;
-
- parent = parent->next;
-
- parse_commit(p);
- if (p->object.flags & SEEN)
- continue;
- p->object.flags |= SEEN;
- insert_by_date(p, list);
- }
-}
-
-static struct commit_list *limit_list(struct commit_list *list)
-{
- struct commit_list *newlist = NULL;
- struct commit_list **p = &newlist;
- while (list) {
- struct commit_list *entry = list;
- struct commit *commit = list->item;
- struct object *obj = &commit->object;
-
- list = list->next;
- free(entry);
-
- if (max_age != -1 && (commit->date < max_age))
- obj->flags |= UNINTERESTING;
- if (unpacked && has_sha1_pack(obj->sha1))
- obj->flags |= UNINTERESTING;
- add_parents_to_list(commit, &list);
- if (obj->flags & UNINTERESTING) {
- mark_parents_uninteresting(commit);
- if (everybody_uninteresting(list))
- break;
- continue;
- }
- if (min_age != -1 && (commit->date > min_age))
- continue;
- p = &commit_list_insert(commit, p)->next;
- }
- if (tree_objects)
- mark_edges_uninteresting(newlist);
- if (bisect_list)
- newlist = find_bisection(newlist);
- return newlist;
-}
-
-static void add_pending_object(struct object *obj, const char *name)
-{
- add_object(obj, &pending_objects, NULL, name);
-}
-
-static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags)
-{
- struct object *object;
-
- object = parse_object(sha1);
- if (!object)
- die("bad object %s", name);
-
- /*
- * Tag object? Look what it points to..
- */
- while (object->type == tag_type) {
- struct tag *tag = (struct tag *) object;
- object->flags |= flags;
- if (tag_objects && !(object->flags & UNINTERESTING))
- add_pending_object(object, tag->tag);
- object = parse_object(tag->tagged->sha1);
- if (!object)
- die("bad object %s", sha1_to_hex(tag->tagged->sha1));
- }
-
- /*
- * Commit object? Just return it, we'll do all the complex
- * reachability crud.
- */
- if (object->type == commit_type) {
- struct commit *commit = (struct commit *)object;
- object->flags |= flags;
- if (parse_commit(commit) < 0)
- die("unable to parse commit %s", name);
- if (flags & UNINTERESTING)
- mark_parents_uninteresting(commit);
- return commit;
- }
-
- /*
- * Tree object? Either mark it uniniteresting, or add it
- * to the list of objects to look at later..
- */
- if (object->type == tree_type) {
- struct tree *tree = (struct tree *)object;
- if (!tree_objects)
- return NULL;
- if (flags & UNINTERESTING) {
- mark_tree_uninteresting(tree);
- return NULL;
- }
- add_pending_object(object, "");
- return NULL;
- }
-
- /*
- * Blob object? You know the drill by now..
- */
- if (object->type == blob_type) {
- struct blob *blob = (struct blob *)object;
- if (!blob_objects)
- return NULL;
- if (flags & UNINTERESTING) {
- mark_blob_uninteresting(blob);
- return NULL;
- }
- add_pending_object(object, "");
- return NULL;
- }
- die("%s is unknown object", name);
-}
-
-static void handle_one_commit(struct commit *com, struct commit_list **lst)
-{
- if (!com || com->object.flags & SEEN)
- return;
- com->object.flags |= SEEN;
- commit_list_insert(com, lst);
-}
-
-/* for_each_ref() callback does not allow user data -- Yuck. */
-static struct commit_list **global_lst;
-
-static int include_one_commit(const char *path, const unsigned char *sha1)
-{
- struct commit *com = get_commit_reference(path, sha1, 0);
- handle_one_commit(com, global_lst);
- return 0;
-}
-
-static void handle_all(struct commit_list **lst)
-{
- global_lst = lst;
- for_each_ref(include_one_commit);
- global_lst = NULL;
-}
-
int main(int argc, const char **argv)
{
- const char *prefix = setup_git_directory();
- struct commit_list *list = NULL;
- int i, limited = 0;
+ struct commit_list *list;
+ int i;
+
+ argc = setup_revisions(argc, argv, &revs, NULL);
for (i = 1 ; i < argc; i++) {
- int flags;
const char *arg = argv[i];
- char *dotdot;
- struct commit *commit;
- unsigned char sha1[20];
/* accept -<digit>, like traditilnal "head" */
if ((*arg == '-') && isdigit(arg[1])) {
- max_count = atoi(arg + 1);
+ revs.max_count = atoi(arg + 1);
continue;
}
if (!strcmp(arg, "-n")) {
if (++i >= argc)
die("-n requires an argument");
- max_count = atoi(argv[i]);
+ revs.max_count = atoi(argv[i]);
continue;
}
if (!strncmp(arg,"-n",2)) {
- max_count = atoi(arg + 2);
- continue;
- }
- if (!strncmp(arg, "--max-count=", 12)) {
- max_count = atoi(arg + 12);
- continue;
- }
- if (!strncmp(arg, "--max-age=", 10)) {
- max_age = atoi(arg + 10);
- limited = 1;
- continue;
- }
- if (!strncmp(arg, "--min-age=", 10)) {
- min_age = atoi(arg + 10);
- limited = 1;
+ revs.max_count = atoi(arg + 2);
continue;
}
if (!strcmp(arg, "--header")) {
@@ -881,10 +331,6 @@ int main(int argc, const char **argv)
commit_prefix = "commit ";
continue;
}
- if (!strncmp(arg, "--no-merges", 11)) {
- no_merges = 1;
- continue;
- }
if (!strcmp(arg, "--parents")) {
show_parents = 1;
continue;
@@ -893,140 +339,27 @@ int main(int argc, const char **argv)
bisect_list = 1;
continue;
}
- if (!strcmp(arg, "--all")) {
- handle_all(&list);
- continue;
- }
- if (!strcmp(arg, "--objects")) {
- tag_objects = 1;
- tree_objects = 1;
- blob_objects = 1;
- continue;
- }
- if (!strcmp(arg, "--objects-edge")) {
- tag_objects = 1;
- tree_objects = 1;
- blob_objects = 1;
- edge_hint = 1;
- continue;
- }
- if (!strcmp(arg, "--unpacked")) {
- unpacked = 1;
- limited = 1;
- continue;
- }
- if (!strcmp(arg, "--merge-order")) {
- merge_order = 1;
- continue;
- }
- if (!strcmp(arg, "--show-breaks")) {
- show_breaks = 1;
- continue;
- }
- if (!strcmp(arg, "--topo-order")) {
- topo_order = 1;
- lifo = 1;
- limited = 1;
- continue;
- }
- if (!strcmp(arg, "--date-order")) {
- topo_order = 1;
- lifo = 0;
- limited = 1;
- continue;
- }
- if (!strcmp(arg, "--dense")) {
- dense = 1;
- continue;
- }
- if (!strcmp(arg, "--sparse")) {
- dense = 0;
- continue;
- }
- if (!strcmp(arg, "--remove-empty")) {
- remove_empty_trees = 1;
- continue;
- }
- if (!strcmp(arg, "--")) {
- i++;
- break;
- }
-
- if (show_breaks && !merge_order)
- usage(rev_list_usage);
+ usage(rev_list_usage);
- flags = 0;
- dotdot = strstr(arg, "..");
- if (dotdot) {
- unsigned char from_sha1[20];
- char *next = dotdot + 2;
- *dotdot = 0;
- if (!*next)
- next = "HEAD";
- if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
- struct commit *exclude;
- struct commit *include;
-
- exclude = get_commit_reference(arg, from_sha1, UNINTERESTING);
- include = get_commit_reference(next, sha1, 0);
- if (!exclude || !include)
- die("Invalid revision range %s..%s", arg, next);
- limited = 1;
- handle_one_commit(exclude, &list);
- handle_one_commit(include, &list);
- continue;
- }
- *dotdot = '.';
- }
- if (*arg == '^') {
- flags = UNINTERESTING;
- arg++;
- limited = 1;
- }
- if (get_sha1(arg, sha1) < 0) {
- struct stat st;
- if (lstat(arg, &st) < 0)
- die("'%s': %s", arg, strerror(errno));
- break;
- }
- commit = get_commit_reference(arg, sha1, flags);
- handle_one_commit(commit, &list);
}
+ list = revs.commits;
+
if (!list &&
- (!(tag_objects||tree_objects||blob_objects) && !pending_objects))
+ (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects))
usage(rev_list_usage);
- paths = get_pathspec(prefix, argv + i);
- if (paths) {
- limited = 1;
- diff_tree_setup_paths(paths);
- }
+ prepare_revision_walk(&revs);
+ if (revs.tree_objects)
+ mark_edges_uninteresting(revs.commits);
+
+ if (bisect_list)
+ revs.commits = find_bisection(revs.commits);
save_commit_buffer = verbose_header;
track_object_refs = 0;
- if (!merge_order) {
- sort_by_date(&list);
- if (list && !limited && max_count == 1 &&
- !tag_objects && !tree_objects && !blob_objects) {
- show_commit(list->item);
- return 0;
- }
- if (limited)
- list = limit_list(list);
- if (topo_order)
- sort_in_topological_order(&list, lifo);
- show_commit_list(list);
- } else {
-#ifndef NO_OPENSSL
- if (sort_list_in_merge_order(list, &process_commit)) {
- die("merge order sort failed\n");
- }
-#else
- die("merge order sort unsupported, OpenSSL not linked");
-#endif
- }
+ show_commit_list(&revs);
return 0;
}
diff --git a/rev-parse.c b/rev-parse.c
index 610eacb35a..f90e999e60 100644
--- a/rev-parse.c
+++ b/rev-parse.c
@@ -39,14 +39,12 @@ static int is_rev_argument(const char *arg)
"--header",
"--max-age=",
"--max-count=",
- "--merge-order",
"--min-age=",
"--no-merges",
"--objects",
"--objects-edge",
"--parents",
"--pretty",
- "--show-breaks",
"--sparse",
"--topo-order",
"--date-order",
diff --git a/revision.c b/revision.c
new file mode 100644
index 0000000000..a3df810076
--- /dev/null
+++ b/revision.c
@@ -0,0 +1,722 @@
+#include "cache.h"
+#include "tag.h"
+#include "blob.h"
+#include "tree.h"
+#include "commit.h"
+#include "diff.h"
+#include "refs.h"
+#include "revision.h"
+
+static char *path_name(struct name_path *path, const char *name)
+{
+ struct name_path *p;
+ char *n, *m;
+ int nlen = strlen(name);
+ int len = nlen + 1;
+
+ for (p = path; p; p = p->up) {
+ if (p->elem_len)
+ len += p->elem_len + 1;
+ }
+ n = xmalloc(len);
+ m = n + len - (nlen + 1);
+ strcpy(m, name);
+ for (p = path; p; p = p->up) {
+ if (p->elem_len) {
+ m -= p->elem_len + 1;
+ memcpy(m, p->elem, p->elem_len);
+ m[p->elem_len] = '/';
+ }
+ }
+ return n;
+}
+
+struct object_list **add_object(struct object *obj,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name)
+{
+ struct object_list *entry = xmalloc(sizeof(*entry));
+ entry->item = obj;
+ entry->next = *p;
+ entry->name = path_name(path, name);
+ *p = entry;
+ return &entry->next;
+}
+
+static void mark_blob_uninteresting(struct blob *blob)
+{
+ if (blob->object.flags & UNINTERESTING)
+ return;
+ blob->object.flags |= UNINTERESTING;
+}
+
+void mark_tree_uninteresting(struct tree *tree)
+{
+ struct object *obj = &tree->object;
+ struct tree_entry_list *entry;
+
+ if (obj->flags & UNINTERESTING)
+ return;
+ obj->flags |= UNINTERESTING;
+ if (!has_sha1_file(obj->sha1))
+ return;
+ if (parse_tree(tree) < 0)
+ die("bad tree %s", sha1_to_hex(obj->sha1));
+ entry = tree->entries;
+ tree->entries = NULL;
+ while (entry) {
+ struct tree_entry_list *next = entry->next;
+ if (entry->directory)
+ mark_tree_uninteresting(entry->item.tree);
+ else
+ mark_blob_uninteresting(entry->item.blob);
+ free(entry);
+ entry = next;
+ }
+}
+
+void mark_parents_uninteresting(struct commit *commit)
+{
+ struct commit_list *parents = commit->parents;
+
+ while (parents) {
+ struct commit *commit = parents->item;
+ commit->object.flags |= UNINTERESTING;
+
+ /*
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->parents)
+ mark_parents_uninteresting(commit);
+
+ /*
+ * A missing commit is ok iff its parent is marked
+ * uninteresting.
+ *
+ * We just mark such a thing parsed, so that when
+ * it is popped next time around, we won't be trying
+ * to parse it and get an error.
+ */
+ if (!has_sha1_file(commit->object.sha1))
+ commit->object.parsed = 1;
+ parents = parents->next;
+ }
+}
+
+static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name)
+{
+ add_object(obj, &revs->pending_objects, NULL, name);
+}
+
+static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags)
+{
+ struct object *object;
+
+ object = parse_object(sha1);
+ if (!object)
+ die("bad object %s", name);
+
+ /*
+ * Tag object? Look what it points to..
+ */
+ while (object->type == tag_type) {
+ struct tag *tag = (struct tag *) object;
+ object->flags |= flags;
+ if (revs->tag_objects && !(object->flags & UNINTERESTING))
+ add_pending_object(revs, object, tag->tag);
+ object = parse_object(tag->tagged->sha1);
+ if (!object)
+ die("bad object %s", sha1_to_hex(tag->tagged->sha1));
+ }
+
+ /*
+ * Commit object? Just return it, we'll do all the complex
+ * reachability crud.
+ */
+ if (object->type == commit_type) {
+ struct commit *commit = (struct commit *)object;
+ object->flags |= flags;
+ if (parse_commit(commit) < 0)
+ die("unable to parse commit %s", name);
+ if (flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit);
+ revs->limited = 1;
+ }
+ return commit;
+ }
+
+ /*
+ * Tree object? Either mark it uniniteresting, or add it
+ * to the list of objects to look at later..
+ */
+ if (object->type == tree_type) {
+ struct tree *tree = (struct tree *)object;
+ if (!revs->tree_objects)
+ return NULL;
+ if (flags & UNINTERESTING) {
+ mark_tree_uninteresting(tree);
+ return NULL;
+ }
+ add_pending_object(revs, object, "");
+ return NULL;
+ }
+
+ /*
+ * Blob object? You know the drill by now..
+ */
+ if (object->type == blob_type) {
+ struct blob *blob = (struct blob *)object;
+ if (!revs->blob_objects)
+ return NULL;
+ if (flags & UNINTERESTING) {
+ mark_blob_uninteresting(blob);
+ return NULL;
+ }
+ add_pending_object(revs, object, "");
+ return NULL;
+ }
+ die("%s is unknown object", name);
+}
+
+static int everybody_uninteresting(struct commit_list *orig)
+{
+ struct commit_list *list = orig;
+ while (list) {
+ struct commit *commit = list->item;
+ list = list->next;
+ if (commit->object.flags & UNINTERESTING)
+ continue;
+ return 0;
+ }
+ return 1;
+}
+
+#define TREE_SAME 0
+#define TREE_NEW 1
+#define TREE_DIFFERENT 2
+static int tree_difference = TREE_SAME;
+
+static void file_add_remove(struct diff_options *options,
+ int addremove, unsigned mode,
+ const unsigned char *sha1,
+ const char *base, const char *path)
+{
+ int diff = TREE_DIFFERENT;
+
+ /*
+ * Is it an add of a new file? It means that
+ * the old tree didn't have it at all, so we
+ * will turn "TREE_SAME" -> "TREE_NEW", but
+ * leave any "TREE_DIFFERENT" alone (and if
+ * it already was "TREE_NEW", we'll keep it
+ * "TREE_NEW" of course).
+ */
+ if (addremove == '+') {
+ diff = tree_difference;
+ if (diff != TREE_SAME)
+ return;
+ diff = TREE_NEW;
+ }
+ tree_difference = diff;
+}
+
+static void file_change(struct diff_options *options,
+ unsigned old_mode, unsigned new_mode,
+ const unsigned char *old_sha1,
+ const unsigned char *new_sha1,
+ const char *base, const char *path)
+{
+ tree_difference = TREE_DIFFERENT;
+}
+
+static struct diff_options diff_opt = {
+ .recursive = 1,
+ .add_remove = file_add_remove,
+ .change = file_change,
+};
+
+static int compare_tree(struct tree *t1, struct tree *t2)
+{
+ if (!t1)
+ return TREE_NEW;
+ if (!t2)
+ return TREE_DIFFERENT;
+ tree_difference = TREE_SAME;
+ if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0)
+ return TREE_DIFFERENT;
+ return tree_difference;
+}
+
+static int same_tree_as_empty(struct tree *t1)
+{
+ int retval;
+ void *tree;
+ struct tree_desc empty, real;
+
+ if (!t1)
+ return 0;
+
+ tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL);
+ if (!tree)
+ return 0;
+ real.buf = tree;
+
+ empty.buf = "";
+ empty.size = 0;
+
+ tree_difference = 0;
+ retval = diff_tree(&empty, &real, "", &diff_opt);
+ free(tree);
+
+ return retval >= 0 && !tree_difference;
+}
+
+static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit)
+{
+ struct commit_list **pp, *parent;
+
+ if (!commit->tree)
+ return;
+
+ if (!commit->parents) {
+ if (!same_tree_as_empty(commit->tree))
+ commit->object.flags |= TREECHANGE;
+ return;
+ }
+
+ pp = &commit->parents;
+ while ((parent = *pp) != NULL) {
+ struct commit *p = parent->item;
+
+ if (p->object.flags & UNINTERESTING) {
+ pp = &parent->next;
+ continue;
+ }
+
+ parse_commit(p);
+ switch (compare_tree(p->tree, commit->tree)) {
+ case TREE_SAME:
+ parent->next = NULL;
+ commit->parents = parent;
+ return;
+
+ case TREE_NEW:
+ if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) {
+ *pp = parent->next;
+ continue;
+ }
+ /* fallthrough */
+ case TREE_DIFFERENT:
+ pp = &parent->next;
+ continue;
+ }
+ die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1));
+ }
+ commit->object.flags |= TREECHANGE;
+}
+
+static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list)
+{
+ struct commit_list *parent = commit->parents;
+
+ /*
+ * If the commit is uninteresting, don't try to
+ * prune parents - we want the maximal uninteresting
+ * set.
+ *
+ * Normally we haven't parsed the parent
+ * yet, so we won't have a parent of a parent
+ * here. However, it may turn out that we've
+ * reached this commit some other way (where it
+ * wasn't uninteresting), in which case we need
+ * to mark its parents recursively too..
+ */
+ if (commit->object.flags & UNINTERESTING) {
+ while (parent) {
+ struct commit *p = parent->item;
+ parent = parent->next;
+ parse_commit(p);
+ p->object.flags |= UNINTERESTING;
+ if (p->parents)
+ mark_parents_uninteresting(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+ return;
+ }
+
+ /*
+ * Ok, the commit wasn't uninteresting. Try to
+ * simplify the commit history and find the parent
+ * that has no differences in the path set if one exists.
+ */
+ if (revs->paths)
+ try_to_simplify_commit(revs, commit);
+
+ parent = commit->parents;
+ while (parent) {
+ struct commit *p = parent->item;
+
+ parent = parent->next;
+
+ parse_commit(p);
+ if (p->object.flags & SEEN)
+ continue;
+ p->object.flags |= SEEN;
+ insert_by_date(p, list);
+ }
+}
+
+static void limit_list(struct rev_info *revs)
+{
+ struct commit_list *list = revs->commits;
+ struct commit_list *newlist = NULL;
+ struct commit_list **p = &newlist;
+
+ if (revs->paths)
+ diff_tree_setup_paths(revs->paths);
+
+ while (list) {
+ struct commit_list *entry = list;
+ struct commit *commit = list->item;
+ struct object *obj = &commit->object;
+
+ list = list->next;
+ free(entry);
+
+ if (revs->max_age != -1 && (commit->date < revs->max_age))
+ obj->flags |= UNINTERESTING;
+ if (revs->unpacked && has_sha1_pack(obj->sha1))
+ obj->flags |= UNINTERESTING;
+ add_parents_to_list(revs, commit, &list);
+ if (obj->flags & UNINTERESTING) {
+ mark_parents_uninteresting(commit);
+ if (everybody_uninteresting(list))
+ break;
+ continue;
+ }
+ if (revs->min_age != -1 && (commit->date > revs->min_age))
+ continue;
+ p = &commit_list_insert(commit, p)->next;
+ }
+ revs->commits = newlist;
+}
+
+static void add_one_commit(struct commit *commit, struct rev_info *revs)
+{
+ if (!commit || (commit->object.flags & SEEN))
+ return;
+ commit->object.flags |= SEEN;
+ commit_list_insert(commit, &revs->commits);
+}
+
+static int all_flags;
+static struct rev_info *all_revs;
+
+static int handle_one_ref(const char *path, const unsigned char *sha1)
+{
+ struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags);
+ add_one_commit(commit, all_revs);
+ return 0;
+}
+
+static void handle_all(struct rev_info *revs, unsigned flags)
+{
+ all_revs = revs;
+ all_flags = flags;
+ for_each_ref(handle_one_ref);
+}
+
+/*
+ * Parse revision information, filling in the "rev_info" structure,
+ * and removing the used arguments from the argument list.
+ *
+ * Returns the number of arguments left that weren't recognized
+ * (which are also moved to the head of the argument list)
+ */
+int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def)
+{
+ int i, flags, seen_dashdash;
+ const char **unrecognized = argv + 1;
+ int left = 1;
+
+ memset(revs, 0, sizeof(*revs));
+ revs->lifo = 1;
+ revs->dense = 1;
+ revs->prefix = setup_git_directory();
+ revs->max_age = -1;
+ revs->min_age = -1;
+ revs->max_count = -1;
+
+ /* First, search for "--" */
+ seen_dashdash = 0;
+ for (i = 1; i < argc; i++) {
+ const char *arg = argv[i];
+ if (strcmp(arg, "--"))
+ continue;
+ argv[i] = NULL;
+ argc = i;
+ revs->paths = get_pathspec(revs->prefix, argv + i + 1);
+ seen_dashdash = 1;
+ break;
+ }
+
+ flags = 0;
+ for (i = 1; i < argc; i++) {
+ struct commit *commit;
+ const char *arg = argv[i];
+ unsigned char sha1[20];
+ char *dotdot;
+ int local_flags;
+
+ if (*arg == '-') {
+ if (!strncmp(arg, "--max-count=", 12)) {
+ revs->max_count = atoi(arg + 12);
+ continue;
+ }
+ /* accept -<digit>, like traditilnal "head" */
+ if ((*arg == '-') && isdigit(arg[1])) {
+ revs->max_count = atoi(arg + 1);
+ continue;
+ }
+ if (!strcmp(arg, "-n")) {
+ if (argc <= i + 1)
+ die("-n requires an argument");
+ revs->max_count = atoi(argv[++i]);
+ continue;
+ }
+ if (!strncmp(arg,"-n",2)) {
+ revs->max_count = atoi(arg + 2);
+ continue;
+ }
+ if (!strncmp(arg, "--max-age=", 10)) {
+ revs->max_age = atoi(arg + 10);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--min-age=", 10)) {
+ revs->min_age = atoi(arg + 10);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--since=", 8)) {
+ revs->max_age = approxidate(arg + 8);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--after=", 8)) {
+ revs->max_age = approxidate(arg + 8);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--before=", 9)) {
+ revs->min_age = approxidate(arg + 9);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--until=", 8)) {
+ revs->min_age = approxidate(arg + 8);
+ revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--all")) {
+ handle_all(revs, flags);
+ continue;
+ }
+ if (!strcmp(arg, "--not")) {
+ flags ^= UNINTERESTING;
+ continue;
+ }
+ if (!strcmp(arg, "--default")) {
+ if (++i >= argc)
+ die("bad --default argument");
+ def = argv[i];
+ continue;
+ }
+ if (!strcmp(arg, "--topo-order")) {
+ revs->topo_order = 1;
+ revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--date-order")) {
+ revs->lifo = 0;
+ revs->topo_order = 1;
+ revs->limited = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--dense")) {
+ revs->dense = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--sparse")) {
+ revs->dense = 0;
+ continue;
+ }
+ if (!strcmp(arg, "--remove-empty")) {
+ revs->remove_empty_trees = 1;
+ continue;
+ }
+ if (!strncmp(arg, "--no-merges", 11)) {
+ revs->no_merges = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--objects")) {
+ revs->tag_objects = 1;
+ revs->tree_objects = 1;
+ revs->blob_objects = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--objects-edge")) {
+ revs->tag_objects = 1;
+ revs->tree_objects = 1;
+ revs->blob_objects = 1;
+ revs->edge_hint = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--unpacked")) {
+ revs->unpacked = 1;
+ revs->limited = 1;
+ continue;
+ }
+ *unrecognized++ = arg;
+ left++;
+ continue;
+ }
+ dotdot = strstr(arg, "..");
+ if (dotdot) {
+ unsigned char from_sha1[20];
+ char *next = dotdot + 2;
+ *dotdot = 0;
+ if (!*next)
+ next = "HEAD";
+ if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) {
+ struct commit *exclude;
+ struct commit *include;
+
+ exclude = get_commit_reference(revs, arg, from_sha1, flags ^ UNINTERESTING);
+ include = get_commit_reference(revs, next, sha1, flags);
+ if (!exclude || !include)
+ die("Invalid revision range %s..%s", arg, next);
+ add_one_commit(exclude, revs);
+ add_one_commit(include, revs);
+ continue;
+ }
+ *dotdot = '.';
+ }
+ local_flags = 0;
+ if (*arg == '^') {
+ local_flags = UNINTERESTING;
+ arg++;
+ }
+ if (get_sha1(arg, sha1) < 0) {
+ struct stat st;
+ int j;
+
+ if (seen_dashdash || local_flags)
+ die("bad revision '%s'", arg);
+
+ /* If we didn't have a "--", all filenames must exist */
+ for (j = i; j < argc; j++) {
+ if (lstat(argv[j], &st) < 0)
+ die("'%s': %s", arg, strerror(errno));
+ }
+ revs->paths = get_pathspec(revs->prefix, argv + i);
+ break;
+ }
+ commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags);
+ add_one_commit(commit, revs);
+ }
+ if (def && !revs->commits) {
+ unsigned char sha1[20];
+ struct commit *commit;
+ if (get_sha1(def, sha1) < 0)
+ die("bad default revision '%s'", def);
+ commit = get_commit_reference(revs, def, sha1, 0);
+ add_one_commit(commit, revs);
+ }
+ if (revs->paths)
+ revs->limited = 1;
+ return left;
+}
+
+void prepare_revision_walk(struct rev_info *revs)
+{
+ sort_by_date(&revs->commits);
+ if (revs->limited)
+ limit_list(revs);
+ if (revs->topo_order)
+ sort_in_topological_order(&revs->commits, revs->lifo);
+}
+
+static int rewrite_one(struct commit **pp)
+{
+ for (;;) {
+ struct commit *p = *pp;
+ if (p->object.flags & (TREECHANGE | UNINTERESTING))
+ return 0;
+ if (!p->parents)
+ return -1;
+ *pp = p->parents->item;
+ }
+}
+
+static void rewrite_parents(struct commit *commit)
+{
+ struct commit_list **pp = &commit->parents;
+ while (*pp) {
+ struct commit_list *parent = *pp;
+ if (rewrite_one(&parent->item) < 0) {
+ *pp = parent->next;
+ continue;
+ }
+ pp = &parent->next;
+ }
+}
+
+struct commit *get_revision(struct rev_info *revs)
+{
+ struct commit_list *list = revs->commits;
+ struct commit *commit;
+
+ if (!list)
+ return NULL;
+
+ /* Check the max_count ... */
+ commit = list->item;
+ switch (revs->max_count) {
+ case -1:
+ break;
+ case 0:
+ return NULL;
+ default:
+ revs->max_count--;
+ }
+
+ do {
+ commit = pop_most_recent_commit(&revs->commits, SEEN);
+ if (commit->object.flags & (UNINTERESTING|SHOWN))
+ continue;
+ if (revs->min_age != -1 && (commit->date > revs->min_age))
+ continue;
+ if (revs->max_age != -1 && (commit->date < revs->max_age))
+ return NULL;
+ if (revs->no_merges && commit->parents && commit->parents->next)
+ continue;
+ if (revs->paths && revs->dense) {
+ if (!(commit->object.flags & TREECHANGE))
+ continue;
+ rewrite_parents(commit);
+ }
+ commit->object.flags |= SHOWN;
+ return commit;
+ } while (revs->commits);
+ return NULL;
+}
diff --git a/revision.h b/revision.h
new file mode 100644
index 0000000000..31e8f61567
--- /dev/null
+++ b/revision.h
@@ -0,0 +1,57 @@
+#ifndef REVISION_H
+#define REVISION_H
+
+#define SEEN (1u<<0)
+#define UNINTERESTING (1u<<1)
+#define TREECHANGE (1u<<2)
+#define SHOWN (1u<<3)
+#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */
+
+struct rev_info {
+ /* Starting list */
+ struct commit_list *commits;
+ struct object_list *pending_objects;
+
+ /* Basic information */
+ const char *prefix;
+ const char **paths;
+
+ /* Traversal flags */
+ unsigned int dense:1,
+ no_merges:1,
+ remove_empty_trees:1,
+ lifo:1,
+ topo_order:1,
+ tag_objects:1,
+ tree_objects:1,
+ blob_objects:1,
+ edge_hint:1,
+ limited:1,
+ unpacked:1;
+
+ /* special limits */
+ int max_count;
+ unsigned long max_age;
+ unsigned long min_age;
+};
+
+/* revision.c */
+extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def);
+extern void prepare_revision_walk(struct rev_info *revs);
+extern struct commit *get_revision(struct rev_info *revs);
+
+extern void mark_parents_uninteresting(struct commit *commit);
+extern void mark_tree_uninteresting(struct tree *tree);
+
+struct name_path {
+ struct name_path *up;
+ int elem_len;
+ const char *elem;
+};
+
+extern struct object_list **add_object(struct object *obj,
+ struct object_list **p,
+ struct name_path *path,
+ const char *name);
+
+#endif
diff --git a/show-branch.c b/show-branch.c
index 5a86ae2f9e..24efb65e62 100644
--- a/show-branch.c
+++ b/show-branch.c
@@ -5,7 +5,7 @@
#include "refs.h"
static const char show_branch_usage[] =
-"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [<refs>...]";
+"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]";
static int default_num = 0;
static int default_alloc = 0;
@@ -547,6 +547,7 @@ int main(int ac, char **av)
int shown_merge_point = 0;
int with_current_branch = 0;
int head_at = -1;
+ int topics = 0;
setup_git_directory();
git_config(git_show_branch_config);
@@ -587,6 +588,8 @@ int main(int ac, char **av)
independent = 1;
else if (!strcmp(arg, "--topo-order"))
lifo = 1;
+ else if (!strcmp(arg, "--topics"))
+ topics = 1;
else if (!strcmp(arg, "--date-order"))
lifo = 0;
else
@@ -724,11 +727,17 @@ int main(int ac, char **av)
while (seen) {
struct commit *commit = pop_one_commit(&seen);
int this_flag = commit->object.flags;
+ int is_merge_point = ((this_flag & all_revs) == all_revs);
- shown_merge_point |= ((this_flag & all_revs) == all_revs);
+ shown_merge_point |= is_merge_point;
if (1 < num_rev) {
int is_merge = !!(commit->parents && commit->parents->next);
+ if (topics &&
+ !is_merge_point &&
+ (this_flag & (1u << REV_SHIFT)))
+ continue;
+
for (i = 0; i < num_rev; i++) {
int mark;
if (!(this_flag & (1u << (i + REV_SHIFT))))
diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh
index cabfadd56d..d1947e11c1 100755
--- a/t/t3600-rm.sh
+++ b/t/t3600-rm.sh
@@ -8,11 +8,20 @@ test_description='Test of the various options to git-rm.'
. ./test-lib.sh
# Setup some files to be removed, some with funny characters
-touch -- foo bar baz 'space embedded' 'tab embedded' 'newline
-embedded' -q
-git-add -- foo bar baz 'space embedded' 'tab embedded' 'newline
-embedded' -q
-git-commit -m "add files"
+touch -- foo bar baz 'space embedded' -q
+git-add -- foo bar baz 'space embedded' -q
+git-commit -m "add normal files"
+test_tabs=y
+if touch -- 'tab embedded' 'newline
+embedded'
+then
+git-add -- 'tab embedded' 'newline
+embedded'
+git-commit -m "add files with tabs and newlines"
+else
+ say 'Your filesystem does not allow tabs in filenames.'
+ test_tabs=n
+fi
test_expect_success \
'Pre-check that foo exists and is in index before git-rm foo' \
@@ -42,16 +51,18 @@ test_expect_success \
'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \
'git-rm -- -q'
-test_expect_success \
+test "$test_tabs" = y && test_expect_success \
"Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \
"git-rm -f 'space embedded' 'tab embedded' 'newline
embedded'"
+if test "$test_tabs" = y; then
chmod u-w .
test_expect_failure \
'Test that "git-rm -f" fails if its rm fails' \
'git-rm -f baz'
chmod u+w .
+fi
test_expect_success \
'When the rm in "git-rm -f" fails, it should not remove the file from the index' \
diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh
deleted file mode 100755
index 7724e8a8c5..0000000000
--- a/t/t6001-rev-list-merge-order.sh
+++ /dev/null
@@ -1,462 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005 Jon Seymour
-#
-
-test_description='Tests git-rev-list --merge-order functionality'
-
-. ./test-lib.sh
-. ../t6000lib.sh # t6xxx specific functions
-
-# test-case specific test function
-check_adjacency()
-{
- read previous
- echo "= $previous"
- while read next
- do
- if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null)
- then
- echo "^ $next"
- else
- echo "| $next"
- fi
- previous=$next
- done
-}
-
-list_duplicates()
-{
- "$@" | sort | uniq -d
-}
-
-grep_stderr()
-{
- args=$1
- shift 1
- "$@" 2>&1 | grep "$args"
-}
-
-date >path0
-git-update-index --add path0
-save_tag tree git-write-tree
-on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree
-on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root
-on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0
-on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1
-on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2
-on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0
-on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0
-on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1
-on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1
-on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2
-on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2
-on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2
-on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1
-on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2
-on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3
-on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3
-on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4
-on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3
-on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4
-on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3
-on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4
-on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree
-on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root
-on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0
-on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1
-on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5
-
-
-#
-# note: as of 20/6, it isn't possible to create duplicate parents, so this
-# can't be tested.
-#
-#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3
-hide_error save_tag e1 as_author e@example.com unique_commit e1 tree
-save_tag e2 as_author e@example.com unique_commit e2 tree -p e1
-save_tag f1 as_author f@example.com unique_commit f1 tree -p e1
-save_tag e3 as_author e@example.com unique_commit e3 tree -p e2
-save_tag f2 as_author f@example.com unique_commit f2 tree -p f1
-save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2
-save_tag e5 as_author e@example.com unique_commit e5 tree -p e4
-save_tag f3 as_author f@example.com unique_commit f3 tree -p f2
-save_tag f4 as_author f@example.com unique_commit f4 tree -p f3
-save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4
-save_tag f5 as_author f@example.com unique_commit f5 tree -p f4
-save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6
-save_tag e7 as_author e@example.com unique_commit e7 tree -p e6
-save_tag e8 as_author e@example.com unique_commit e8 tree -p e7
-save_tag e9 as_author e@example.com unique_commit e9 tree -p e8
-save_tag f7 as_author f@example.com unique_commit f7 tree -p f6
-save_tag f8 as_author f@example.com unique_commit f8 tree -p f7
-save_tag f9 as_author f@example.com unique_commit f9 tree -p f8
-save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8
-
-hide_error save_tag g0 unique_commit g0 tree
-save_tag g1 unique_commit g1 tree -p g0
-save_tag h1 unique_commit g2 tree -p g0
-save_tag g2 unique_commit g3 tree -p g1 -p h1
-save_tag h2 unique_commit g4 tree -p g2
-save_tag g3 unique_commit g5 tree -p g2
-save_tag g4 unique_commit g6 tree -p g3 -p h2
-
-git-update-ref HEAD $(tag l5)
-
-test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF
-19
-EOF
-
-if git-rev-list --merge-order HEAD 2>&1 | grep 'OpenSSL not linked' >/dev/null
-then
- test_expect_success 'skipping merge-order test' :
- test_done
- exit
-fi
-
-normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ')
-test_expect_success '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]'
-test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF
-= l5
-| l4
-| l3
-= a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-= a0
-| l2
-| l1
-| l0
-= root
-EOF
-
-test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF
-= g4
-| h2
-^ g3
-= g2
-| h1
-^ g1
-= g0
-EOF
-
-test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-l1
-l0
-root
-EOF
-
-test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-EOF
-
-test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF
-c3
-c2
-c1
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF
-l5
-l4
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF
-l5
-l4
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-EOF
-
-test_output_expect_success 'prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF
-a4
-b4
-b3
-a3
-a2
-a1
-EOF
-
-test_output_expect_success "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF
-= root
-EOF
-
-test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF
-= l0
-= root
-EOF
-
-test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF
-= l1
-| l0
-= root
-EOF
-
-test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF
-^ l2
-| l1
-| l0
-EOF
-
-test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF
-^ l2
-| l1
-EOF
-
-test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF
-^ l2
-EOF
-
-test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF
-^ l5
-| l4
-| l3
-EOF
-
-test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF
-^ l5
-| l4
-EOF
-
-test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF
-^ l5
-EOF
-
-test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF
-= l5
-| l4
-| l3
-= a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-EOF
-
-test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF
-l5
-l4
-l3
-a4
-b4
-a3
-a2
-c3
-c2
-b3
-EOF
-
-test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF
-l5
-l4
-l3
-a4
-b4
-a3
-a2
-c3
-EOF
-
-test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF
-l5
-l4
-l3
-a4
-c3
-b4
-a3
-a2
-EOF
-
-test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF
-EOF
-
-test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF
-EOF
-
-test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF
-EOF
-
-test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF
-EOF
-
-test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF
-EOF
-
-#
-# can't test this now - duplicate parents can't be created
-#
-#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF
-#= m3 c3 a4 c3
-#| a4 c3 b4 a3
-#| b4 a3 b3
-#| b3 b2
-#^ a3 a2
-#| a2 a1
-#| a1 a0
-#^ c3 c2
-#| c2 b2 c1
-#| b2 b1
-#^ c1 b1
-#| b1 a0
-#= a0 l2
-#| l2 l1
-#| l1 l0
-#| l0 root
-#= root
-#EOF
-
-test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF
-EOF
-
-test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF
-= l5r1
-| r1
-| r0
-| alt_root
-^ l5
-| l4
-| l3
-| a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-| a0
-| l2
-| l1
-| l0
-= root
-EOF
-
-test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF
-= r1l5
-| l5
-| l4
-| l3
-| a4
-| c3
-| c2
-| c1
-^ b4
-| b3
-| b2
-| b1
-^ a3
-| a2
-| a1
-| a0
-| l2
-| l1
-| l0
-| root
-^ r1
-| r0
-= alt_root
-EOF
-
-test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF
-a3
-a2
-a1
-EOF
-
-test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF
-l3
-a4
-c3
-c2
-c1
-b4
-b3
-b2
-b1
-a3
-a2
-a1
-a0
-l2
-l1
-l0
-root
-EOF
-
-#
-#
-
-test_done
diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh
index 43d74c502e..811a4797a5 100755
--- a/t/t7001-mv.sh
+++ b/t/t7001-mv.sh
@@ -11,17 +11,31 @@ test_expect_success \
git-commit -m add -a'
test_expect_success \
- 'moving the file' \
+ 'moving the file out of subdirectory' \
'cd path0 && git-mv COPYING ../path1/COPYING'
# in path0 currently
test_expect_success \
'commiting the change' \
- 'cd .. && git-commit -m move -a'
+ 'cd .. && git-commit -m move-out -a'
test_expect_success \
'checking the commit' \
'git-diff-tree -r -M --name-status HEAD^ HEAD | \
grep -E "^R100.+path0/COPYING.+path1/COPYING"'
+test_expect_success \
+ 'moving the file back into subdirectory' \
+ 'cd path0 && git-mv ../path1/COPYING COPYING'
+
+# in path0 currently
+test_expect_success \
+ 'commiting the change' \
+ 'cd .. && git-commit -m move-in -a'
+
+test_expect_success \
+ 'checking the commit' \
+ 'git-diff-tree -r -M --name-status HEAD^ HEAD | \
+ grep -E "^R100.+path1/COPYING.+path0/COPYING"'
+
test_done
diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh
new file mode 100755
index 0000000000..172908a5b0
--- /dev/null
+++ b/t/t8001-annotate.sh
@@ -0,0 +1,90 @@
+#!/bin/sh
+
+test_description='git-annotate'
+. ./test-lib.sh
+
+test_expect_success \
+ 'prepare reference tree' \
+ 'echo "1A quick brown fox jumps over the" >file &&
+ echo "lazy dog" >>file &&
+ git add file
+ GIT_AUTHOR_NAME="A" git commit -a -m "Initial."'
+
+test_expect_success \
+ 'check all lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
+
+test_expect_success \
+ 'Setup new lines blamed on B' \
+ 'echo "2A quick brown fox jumps over the" >>file &&
+ echo "lazy dog" >> file &&
+ GIT_AUTHOR_NAME="B" git commit -a -m "Second."'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]'
+
+test_expect_success \
+ 'Two lines blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]'
+
+test_expect_success \
+ 'merge-setup part 1' \
+ 'git checkout -b branch1 master &&
+ echo "3A slow green fox jumps into the" >> file &&
+ echo "well." >> file &&
+ GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+ 'Two lines blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]'
+
+test_expect_success \
+ 'Two lines blamed on B1' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
+
+test_expect_success \
+ 'merge-setup part 2' \
+ 'git checkout -b branch2 master &&
+ sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new &&
+ mv file.new file &&
+ GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+ 'One line blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
+
+test_expect_success \
+ 'One line blamed on B2' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+
+
+test_expect_success \
+ 'merge-setup part 3' \
+ 'git pull . branch1'
+
+test_expect_success \
+ 'Two lines blamed on A' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]'
+
+test_expect_success \
+ 'One line blamed on B' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]'
+
+test_expect_success \
+ 'Two lines blamed on B1' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]'
+
+test_expect_success \
+ 'One line blamed on B2' \
+ '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]'
+
+test_done
diff --git a/tar-tree.c b/tar-tree.c
index e85a1ed660..e478e13e28 100644
--- a/tar-tree.c
+++ b/tar-tree.c
@@ -304,9 +304,11 @@ static void write_header(const unsigned char *sha1, char typeflag, const char *b
}
if (S_ISDIR(mode))
- mode |= 0755; /* GIT doesn't store permissions of dirs */
- if (S_ISLNK(mode))
- mode |= 0777; /* ... nor of symlinks */
+ mode |= 0777;
+ else if (S_ISREG(mode))
+ mode |= (mode & 0100) ? 0777 : 0666;
+ else if (S_ISLNK(mode))
+ mode |= 0777;
sprintf(&header[100], "%07o", mode & 07777);
/* XXX: should we provide more meaningful info here? */
@@ -391,7 +393,7 @@ int main(int argc, char **argv)
usage(tar_tree_usage);
}
- commit = lookup_commit_reference(sha1);
+ commit = lookup_commit_reference_gently(sha1, 1);
if (commit) {
write_global_extended_header(commit->object.sha1);
archive_time = commit->date;
diff --git a/update-index.c b/update-index.c
index ce1db38d16..797245ab27 100644
--- a/update-index.c
+++ b/update-index.c
@@ -577,9 +577,11 @@ int main(int argc, const char **argv)
break;
}
if (!strcmp(path, "--index-info")) {
+ if (i != argc - 1)
+ die("--index-info must be at the end");
allow_add = allow_replace = allow_remove = 1;
read_index_info(line_termination);
- continue;
+ break;
}
if (!strcmp(path, "--ignore-missing")) {
not_new = 1;