aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--Documentation/Makefile16
-rw-r--r--Documentation/RelNotes-1.6.2.3.txt22
-rw-r--r--Documentation/RelNotes-1.6.3.txt44
-rw-r--r--Documentation/config.txt2
-rw-r--r--Documentation/git-branch.txt4
-rw-r--r--Documentation/git-checkout.txt10
-rw-r--r--Documentation/git-filter-branch.txt3
-rw-r--r--Documentation/git-for-each-ref.txt5
-rw-r--r--Documentation/git-remote.txt4
-rw-r--r--Documentation/git-svn.txt29
-rw-r--r--Documentation/git.txt10
-rw-r--r--Documentation/gitattributes.txt15
-rw-r--r--Documentation/gitignore.txt4
-rw-r--r--Documentation/glossary-content.txt6
-rw-r--r--Makefile31
-rw-r--r--bisect.c556
-rw-r--r--bisect.h29
-rw-r--r--builtin-bisect--helper.c27
-rw-r--r--builtin-branch.c23
-rw-r--r--builtin-checkout.c9
-rw-r--r--builtin-for-each-ref.c164
-rw-r--r--builtin-pack-objects.c10
-rw-r--r--builtin-reflog.c76
-rw-r--r--builtin-remote.c93
-rw-r--r--builtin-rev-list.c596
-rw-r--r--builtin.h1
-rw-r--r--config.c2
-rwxr-xr-xcontrib/completion/git-completion.bash1
-rw-r--r--diff.c17
-rwxr-xr-xgit-bisect.sh94
-rwxr-xr-xgit-pull.sh42
-rwxr-xr-xgit-svn.perl102
-rw-r--r--git.c6
-rw-r--r--http-push.c60
-rw-r--r--http.c19
-rw-r--r--http.h7
-rw-r--r--list-objects.c11
-rw-r--r--list-objects.h6
-rw-r--r--merge-recursive.c16
-rw-r--r--patch-ids.c93
-rw-r--r--perl/Git.pm12
-rw-r--r--quote.c35
-rw-r--r--quote.h9
-rw-r--r--reachable.c1
-rw-r--r--refs.c110
-rw-r--r--refs.h2
-rw-r--r--remote.c13
-rw-r--r--remote.h1
-rw-r--r--sha1-lookup.c101
-rw-r--r--sha1-lookup.h7
-rw-r--r--t/lib-git-svn.sh4
-rwxr-xr-xt/t1303-wacky-config.sh9
-rwxr-xr-xt/t2013-checkout-submodule.sh42
-rwxr-xr-xt/t3101-ls-tree-dirname.sh6
-rwxr-xr-xt/t3701-add-interactive.sh5
-rwxr-xr-xt/t4010-diff-pathspec.sh8
-rwxr-xr-xt/t5506-remote-groups.sh81
-rwxr-xr-xt/t6030-bisect-porcelain.sh60
-rwxr-xr-xt/t6300-for-each-ref.sh22
-rwxr-xr-xt/t7201-co.sh2
-rwxr-xr-xt/t7405-submodule-merge.sh74
-rwxr-xr-xt/t7501-commit.sh4
-rwxr-xr-xt/t9001-send-email.sh5
-rwxr-xr-xt/t9134-git-svn-ignore-paths.sh55
-rwxr-xr-xt/t9200-git-cvsexportcommit.sh5
-rwxr-xr-xt/t9400-git-cvsserver-server.sh4
-rwxr-xr-xt/t9401-git-cvsserver-crlf.sh5
-rwxr-xr-xt/t9500-gitweb-standalone-no-errors.sh5
-rwxr-xr-xt/t9600-cvsimport.sh5
-rwxr-xr-xt/t9700-perl-git.sh5
-rw-r--r--t/test-lib.sh2
-rwxr-xr-xtemplates/hooks--update.sample11
-rw-r--r--tree-diff.c12
-rw-r--r--tree.c8
-rw-r--r--unimplemented.sh4
-rw-r--r--upload-pack.c6
77 files changed, 2050 insertions, 956 deletions
diff --git a/.gitignore b/.gitignore
index 757c7f0ad..41c0b20a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ git-apply
git-archimport
git-archive
git-bisect
+git-bisect--helper
git-blame
git-branch
git-bundle
diff --git a/Documentation/Makefile b/Documentation/Makefile
index dba97dc21..e18242a6d 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -103,6 +103,10 @@ ifdef DOCBOOK_SUPPRESS_SP
XMLTO_EXTRA += -m manpage-suppress-sp.xsl
endif
+SHELL_PATH ?= $(SHELL)
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
#
# Please note that there is a minor bug in asciidoc.
# The version after 6.0.3 _will_ include the patch found here:
@@ -178,7 +182,7 @@ install-pdf: pdf
$(INSTALL) -m 644 user-manual.pdf $(DESTDIR)$(pdfdir)
install-html: html
- sh ./install-webdoc.sh $(DESTDIR)$(htmldir)
+ '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
../GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
$(QUIET_SUBDIR0)../ $(QUIET_SUBDIR1) GIT-VERSION-FILE
@@ -240,7 +244,7 @@ user-manual.xml: user-manual.txt user-manual.conf
technical/api-index.txt: technical/api-index-skel.txt \
technical/api-index.sh $(patsubst %,%.txt,$(API_DOCS))
- $(QUIET_GEN)cd technical && sh ./api-index.sh
+ $(QUIET_GEN)cd technical && '$(SHELL_PATH_SQ)' ./api-index.sh
$(patsubst %,%.html,$(API_DOCS) technical/api-index): %.html : %.txt
$(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 -f asciidoc.conf \
@@ -285,7 +289,7 @@ $(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
$(QUIET_GEN)$(RM) $@+ $@ && \
- sh ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \
+ '$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \
mv $@+ $@
$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
@@ -299,14 +303,14 @@ $(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
mv $@+ $@
install-webdoc : html
- sh ./install-webdoc.sh $(WEBDOC_DEST)
+ '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
quick-install: quick-install-man
quick-install-man:
- sh ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
+ '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
quick-install-html:
- sh ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
+ '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
.PHONY: .FORCE-GIT-VERSION-FILE
diff --git a/Documentation/RelNotes-1.6.2.3.txt b/Documentation/RelNotes-1.6.2.3.txt
new file mode 100644
index 000000000..4d3c1ac91
--- /dev/null
+++ b/Documentation/RelNotes-1.6.2.3.txt
@@ -0,0 +1,22 @@
+GIT v1.6.2.3 Release Notes
+==========================
+
+Fixes since v1.6.2.2
+--------------------
+
+* Setting an octal mode value to core.sharedrepository configuration to
+ restrict access to the repository to group members did not work as
+ advertised.
+
+* A fairly large and trivial memory leak while rev-list shows list of
+ reachable objects has been identified and plugged.
+
+* "git-commit --interactive" did not abort when underlying "git-add -i"
+ signaled a failure.
+
+* git-repack (invoked from git-gc) did not work as nicely as it should in
+ a repository that borrows objects from neighbours via alternates
+ mechanism especially when some packs are marked with the ".keep" flag
+ to prevent them from being repacked.
+
+Many small documentation updates are included as well.
diff --git a/Documentation/RelNotes-1.6.3.txt b/Documentation/RelNotes-1.6.3.txt
index db6956205..839498c38 100644
--- a/Documentation/RelNotes-1.6.3.txt
+++ b/Documentation/RelNotes-1.6.3.txt
@@ -35,11 +35,16 @@ Updates since v1.6.2
(subsystems)
+* various git-svn updates.
+
(performance)
* many uses of lstat(2) in the codepath for "git checkout" have been
optimized out.
+* pruning reflog entries that are unreachable from the tip of the ref
+ during "git reflog prune" (hence "git gc") was very inefficient.
+
(usability, bells and whistles)
* rsync:/path/to/repo can be used to run git over rsync for local
@@ -56,7 +61,7 @@ Updates since v1.6.2
spelled as "--format=<style>". In addition, --format=%formatstring
is a short-hand for --pretty=tformat:%formatstring.
-* "--oneline" is a synonym for "--pretty=oneline --abbrev=commit".
+* "--oneline" is a synonym for "--pretty=oneline --abbrev-commit".
* If you realize that you botched the patch when you are editing hunks
with the 'edit' action in git-add -i/-p, you can abort the editor to
@@ -66,6 +71,10 @@ Updates since v1.6.2
messages given by "git checkout" and "git status" used to count merge
commits; now it doesn't.
+* @{-1} is a new way to refer to the last branch you were on introduced in
+ 1.6.2, but the initial implementation did not teach this to a few
+ commands. Now the syntax works with "branch -m @{-1} newname".
+
* git-archive learned --output=<file> option.
* git-bisect shows not just the number of remaining commits whose goodness
@@ -73,9 +82,12 @@ Updates since v1.6.2
* You can give --date=<format> option to git-blame.
-* git-branch -r shows HEAD symref that points at a remote branch in
+* "git-branch -r" shows HEAD symref that points at a remote branch in
interest of each tracked remote repository.
+* "git-branch -v -v" is a new way to get list of names for branches and the
+ "upstream" branch for them.
+
* git-config learned -e option to open an editor to edit the config file
directly.
@@ -83,15 +95,23 @@ Updates since v1.6.2
* git-fast-export choked when seeing a tag that does not point at commit.
+* git-for-each-ref learned a new "upstream" token.
+
* git-format-patch can be told to use attachment with a new configuration,
format.attach.
* git-format-patch can be told to produce deep or shallow message threads.
+* git-format-patch can be told to always add sign-off with a configuration
+ variable.
+
* git-format-patch learned format.headers configuration to add extra
header fields to the output. This behaviour is similar to the existing
--add-header=<header> option of the command.
+* git-format-patch gives human readable names to the attached files, when
+ told to send patches as attachments.
+
* git-grep learned to highlight the found substrings in color.
* git-imap-send learned to work around Thunderbird's inability to easily
@@ -105,10 +125,8 @@ Updates since v1.6.2
* Output from git-remote command has been vastly improved.
-* git-repack (invoked from git-gc) did not work as nicely as it should in
- a repository that borrows objects from neighbours via alternates
- mechanism especially when some packs are marked with the ".keep" flag
- to prevent them from being repacked.
+* "git remote update --prune $remote" updates from the named remote and
+ then prunes stale tracking branches.
* git-send-email learned --confirm option to review the Cc: list before
sending the messages out.
@@ -141,11 +159,23 @@ v1.6.2.X series.
* The initial checkout did not read the attributes from the .gitattribute
file that is being checked out.
+* "git-checkout <tree-ish> <submodule>" did not update the index entry at
+ the named path; it now does.
+
* git-gc spent excessive amount of time to decide if an object appears
in a locally existing pack (if needed, backport by merging 69e020a).
+* "git-ls-tree" and "git-diff-tree" used a pathspec correctly when
+ deciding to descend into a subdirectory but they did not match the
+ individual paths correctly. This caused pathspecs "abc/d ab" to match
+ "abc/0" ("abc/d" made them decide to descend into the directory "abc/",
+ and then "ab" incorrectly matched "abc/0" when it shouldn't).
+
+* "git-merge-recursive" was broken when a submodule entry was involved in
+ a criss-cross merge situation.
+
---
exec >/var/tmp/1
-O=v1.6.2.2-403-g8130949
+O=v1.6.2.3-497-g54a4749
echo O=$(git describe master)
git shortlog --no-merges $O..master ^maint
diff --git a/Documentation/config.txt b/Documentation/config.txt
index d427dafb4..5319df505 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -1236,7 +1236,7 @@ push.default::
* `matching` push all matching branches.
All branches having the same name in both ends are considered to be
matching. This is the default.
-* `tracking` push the current branch to the branch it is tracking.
+* `tracking` push the current branch to its upstream branch.
* `current` push the current branch to a branch of the same name.
rebase.stat::
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 31ba7f2ad..ba3dea684 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -100,7 +100,9 @@ OPTIONS
-v::
--verbose::
- Show sha1 and commit subject line for each head.
+ Show sha1 and commit subject line for each head, along with
+ relationship to upstream branch (if any). If given twice, print
+ the name of the upstream branch, as well.
--abbrev=<length>::
Alter the sha1's minimum display length in the output listing.
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index 1a6c19e5c..223ea9cae 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -126,9 +126,13 @@ the conflicted merge in the specified paths.
<new_branch>::
Name for the new branch.
+<tree-ish>::
+ Tree to checkout from (when paths are given). If not specified,
+ the index will be used.
+
<branch>::
- Branch to checkout; may be any object ID that resolves to a
- commit. Defaults to HEAD.
+ Branch to checkout (when no paths are given); may be any object
+ ID that resolves to a commit. Defaults to HEAD.
+
When this parameter names a non-branch (but still a valid commit object),
your HEAD becomes 'detached'.
@@ -191,7 +195,7 @@ $ git checkout hello.c <3>
+
<1> switch branch
<2> take a file out of another commit
-<3> restore hello.c from HEAD of current branch
+<3> restore hello.c from the index
+
If you have an unfortunate branch that is named `hello.c`, this
step would be confused as an instruction to switch to that branch.
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index 237f85e76..ab527b5b3 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -31,6 +31,9 @@ changes, which would normally have no effect. Nevertheless, this may be
useful in the future for compensating for some git bugs or such,
therefore such a usage is permitted.
+*NOTE*: This command honors `.git/info/grafts`. If you have any grafts
+defined, running this command will make them permanent.
+
*WARNING*! The rewritten history will have different object names for all
the objects and will not converge with the original branch. You will not
be able to easily push and distribute the rewritten branch on top of the
diff --git a/Documentation/git-for-each-ref.txt b/Documentation/git-for-each-ref.txt
index 5061d3e4e..b362e9ed1 100644
--- a/Documentation/git-for-each-ref.txt
+++ b/Documentation/git-for-each-ref.txt
@@ -85,6 +85,11 @@ objectsize::
objectname::
The object name (aka SHA-1).
+upstream::
+ The name of a local ref which can be considered ``upstream''
+ from the displayed ref. Respects `:short` in the same way as
+ `refname` above.
+
In addition to the above, for commit and tag objects, the header
field names (`tree`, `parent`, `object`, `type`, and `tag`) can
be used to specify the value in the header field.
diff --git a/Documentation/git-remote.txt b/Documentation/git-remote.txt
index c9c0e6f93..9e2b4eaa3 100644
--- a/Documentation/git-remote.txt
+++ b/Documentation/git-remote.txt
@@ -16,7 +16,7 @@ SYNOPSIS
'git remote set-head' <name> [-a | -d | <branch>]
'git remote show' [-n] <name>
'git remote prune' [-n | --dry-run] <name>
-'git remote update' [group]
+'git remote update' [-p | --prune] [group | remote]...
DESCRIPTION
-----------
@@ -125,6 +125,8 @@ the configuration parameter remotes.default will get used; if
remotes.default is not defined, all remotes which do not have the
configuration parameter remote.<name>.skipDefaultUpdate set to true will
be updated. (See linkgit:git-config[1]).
++
+With `--prune` option, prune all the remotes that are updated.
DISCUSSION
diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt
index b7b1af813..9229d45ad 100644
--- a/Documentation/git-svn.txt
+++ b/Documentation/git-svn.txt
@@ -85,6 +85,10 @@ COMMANDS
specified, the prefix must include a trailing slash.
Setting a prefix is useful if you wish to track multiple
projects that share a common repository.
+--ignore-paths=<regex>;;
+ When passed to 'init' or 'clone' this regular expression will
+ be preserved as a config key. See 'fetch' for a description
+ of '--ignore-paths'.
'fetch'::
Fetch unfetched revisions from the Subversion remote we are
@@ -97,6 +101,9 @@ COMMANDS
makes 'git-log' (even without --date=local) show the same times
that `svn log` would in the local timezone.
+--parent;;
+ Fetch only from the SVN parent of the current HEAD.
+
This doesn't interfere with interoperating with the Subversion
repository you cloned from, but if you wish for your local Git
repository to be able to interoperate with someone else's local Git
@@ -104,17 +111,25 @@ repository, either don't use this option or you should both use it in
the same local timezone.
--ignore-paths=<regex>;;
- This allows one to specify Perl regular expression that will
+ This allows one to specify a Perl regular expression that will
cause skipping of all matching paths from checkout from SVN.
- Examples:
+ The '--ignore-paths' option should match for every 'fetch'
+ (including automatic fetches due to 'clone', 'dcommit',
+ 'rebase', etc) on a given repository.
+
+config key: svn-remote.<name>.ignore-paths
+
+ If the ignore-paths config key is set and the command
+ line option is also given, both regular expressions
+ will be used.
- --ignore-paths="^doc" - skip "doc*" directory for every fetch.
+Examples:
- --ignore-paths="^[^/]+/(?:branches|tags)" - skip "branches"
- and "tags" of first level directories.
+ --ignore-paths="^doc" - skip "doc*" directory for every
+ fetch.
- Regular expression is not persistent, you should specify
- it every time when fetching.
+ --ignore-paths="^[^/]+/(?:branches|tags)" - skip
+ "branches" and "tags" of first level directories.
'clone'::
Runs 'init' and 'fetch'. It will automatically create a
diff --git a/Documentation/git.txt b/Documentation/git.txt
index 7513c57c6..470fdc5ec 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -9,7 +9,7 @@ git - the stupid content tracker
SYNOPSIS
--------
[verse]
-'git' [--version] [--exec-path[=GIT_EXEC_PATH]]
+'git' [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path]
[-p|--paginate|--no-pager]
[--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE]
[--help] COMMAND [ARGS]
@@ -43,9 +43,11 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.6.2.1/git.html[documentation for release 1.6.2.1]
+* link:v1.6.2.3/git.html[documentation for release 1.6.2.3]
* release notes for
+ link:RelNotes-1.6.2.3.txt[1.6.2.3],
+ link:RelNotes-1.6.2.2.txt[1.6.2.2],
link:RelNotes-1.6.2.1.txt[1.6.2.1],
link:RelNotes-1.6.2.txt[1.6.2].
@@ -178,6 +180,10 @@ help ...`.
environment variable. If no path is given, 'git' will print
the current setting and then exit.
+--html-path::
+ Print the path to wherever your git HTML documentation is installed
+ and exit.
+
-p::
--paginate::
Pipe all output into 'less' (or if set, $PAGER).
diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 55668e345..aaa073efc 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -60,9 +60,9 @@ same as in `.gitignore` files; see linkgit:gitignore[5].
When deciding what attributes are assigned to a path, git
consults `$GIT_DIR/info/attributes` file (which has the highest
precedence), `.gitattributes` file in the same directory as the
-path in question, and its parent directories (the further the
-directory that contains `.gitattributes` is from the path in
-question, the lower its precedence).
+path in question, and its parent directories up to the toplevel of the
+work tree (the further the directory that contains `.gitattributes`
+is from the path in question, the lower its precedence).
If you wish to affect only a single repository (i.e., to assign
attributes to files that are particular to one user's workflow), then
@@ -297,7 +297,8 @@ for paths.
Then, you would define a "diff.tex.xfuncname" configuration to
specify a regular expression that matches a line that you would
-want to appear as the hunk header "TEXT", like this:
+want to appear as the hunk header "TEXT". Add a section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
------------------------
[diff "tex"]
@@ -345,7 +346,8 @@ split words in a line, by specifying an appropriate regular expression
in the "diff.*.wordRegex" configuration variable. For example, in TeX
a backslash followed by a sequence of letters forms a command, but
several such commands can be run together without intervening
-whitespace. To separate them, use a regular expression such as
+whitespace. To separate them, use a regular expression in your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file) like this:
------------------------
[diff "tex"]
@@ -373,7 +375,8 @@ resulting text on stdout.
For example, to show the diff of the exif information of a
file instead of the binary information (assuming you have the
-exif tool installed):
+exif tool installed), add the following section to your
+`$GIT_DIR/config` file (or `$HOME/.gitconfig` file):
------------------------
[diff "jpg"]
diff --git a/Documentation/gitignore.txt b/Documentation/gitignore.txt
index 59321a2e8..7df3cef46 100644
--- a/Documentation/gitignore.txt
+++ b/Documentation/gitignore.txt
@@ -31,8 +31,8 @@ precedence, the last matching pattern decides the outcome):
* Patterns read from a `.gitignore` file in the same directory
as the path, or in any parent directory, with patterns in the
- higher level files (up to the root) being overridden by those in
- lower level files down to the directory containing the file.
+ higher level files (up to the toplevel of the work tree) being overridden
+ by those in lower level files down to the directory containing the file.
These patterns match relative to the location of the
`.gitignore` file. A project normally includes such
`.gitignore` files in its repository, containing patterns for
diff --git a/Documentation/glossary-content.txt b/Documentation/glossary-content.txt
index 4fc1cf118..572374f7a 100644
--- a/Documentation/glossary-content.txt
+++ b/Documentation/glossary-content.txt
@@ -449,6 +449,12 @@ This commit is referred to as a "merge commit", or sometimes just a
An <<def_object,object>> which is not <<def_reachable,reachable>> from a
<<def_branch,branch>>, <<def_tag,tag>>, or any other reference.
+[[def_upstream_branch]]upstream branch::
+ The default <<def_branch,branch>> that is merged into the branch in
+ question (or the branch in question is rebased onto). It is configured
+ via branch.<name>.remote and branch.<name>.merge. If the upstream branch
+ of 'A' is 'origin/B' sometimes we say "'A' is tracking 'origin/B'".
+
[[def_working_tree]]working tree::
The tree of actual checked out files. The working tree is
normally equal to the <<def_HEAD,HEAD>> plus any local changes
diff --git a/Makefile b/Makefile
index 3e56274d3..57f9f2625 100644
--- a/Makefile
+++ b/Makefile
@@ -145,6 +145,8 @@ all::
# Define NO_PERL_MAKEMAKER if you cannot use Makefiles generated by perl's
# MakeMaker (e.g. using ActiveState under Cygwin).
#
+# Define NO_PERL if you do not want Perl scripts or libraries at all.
+#
# Define NO_TCLTK if you do not want Tcl/Tk GUI.
#
# The TCL_PATH variable governs the location of the Tcl interpreter
@@ -356,7 +358,10 @@ BUILT_INS += git-whatchanged$X
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
# what 'all' will build but not install in gitexecdir
-OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
+OTHER_PROGRAMS = git$X
+ifndef NO_PERL
+OTHER_PROGRAMS += gitweb/gitweb.cgi
+endif
# Set paths to tools early so that they can be used for version tests.
ifndef SHELL_PATH
@@ -435,6 +440,7 @@ LIB_OBJS += archive-tar.o
LIB_OBJS += archive-zip.o
LIB_OBJS += attr.o
LIB_OBJS += base85.o
+LIB_OBJS += bisect.o
LIB_OBJS += blob.o
LIB_OBJS += branch.o
LIB_OBJS += bundle.o
@@ -535,6 +541,7 @@ BUILTIN_OBJS += builtin-add.o
BUILTIN_OBJS += builtin-annotate.o
BUILTIN_OBJS += builtin-apply.o
BUILTIN_OBJS += builtin-archive.o
+BUILTIN_OBJS += builtin-bisect--helper.o
BUILTIN_OBJS += builtin-blame.o
BUILTIN_OBJS += builtin-branch.o
BUILTIN_OBJS += builtin-bundle.o
@@ -1107,6 +1114,10 @@ ifeq ($(TCLTK_PATH),)
NO_TCLTK=NoThanks
endif
+ifeq ($(PERL_PATH),)
+NO_PERL=NoThanks
+endif
+
QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
QUIET_SUBDIR1 =
@@ -1181,7 +1192,9 @@ ifndef NO_TCLTK
$(QUIET_SUBDIR0)git-gui $(QUIET_SUBDIR1) gitexecdir='$(gitexec_instdir_SQ)' all
$(QUIET_SUBDIR0)gitk-git $(QUIET_SUBDIR1) all
endif
+ifndef NO_PERL
$(QUIET_SUBDIR0)perl $(QUIET_SUBDIR1) PERL_PATH='$(PERL_PATH_SQ)' prefix='$(prefix_SQ)' all
+endif
$(QUIET_SUBDIR0)templates $(QUIET_SUBDIR1)
please_set_SHELL_PATH_to_a_more_modern_shell:
@@ -1194,6 +1207,7 @@ strip: $(PROGRAMS) git$X
git.o: git.c common-cmds.h GIT-CFLAGS
$(QUIET_CC)$(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \
+ '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
$(ALL_CFLAGS) -c $(filter %.c,$^)
git$X: git.o $(BUILTIN_OBJS) $(GITLIBS)
@@ -1228,6 +1242,7 @@ $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh
chmod +x $@+ && \
mv $@+ $@
+ifndef NO_PERL
$(patsubst %.perl,%,$(SCRIPT_PERL)): perl/perl.mak
perl/perl.mak: GIT-CFLAGS perl/Makefile perl/Makefile.PL
@@ -1287,6 +1302,15 @@ git-instaweb: git-instaweb.sh gitweb/gitweb.cgi gitweb/gitweb.css
$@.sh > $@+ && \
chmod +x $@+ && \
mv $@+ $@
+else # NO_PERL
+$(patsubst %.perl,%,$(SCRIPT_PERL)) git-instaweb: % : unimplemented.sh
+ $(QUIET_GEN)$(RM) $@ $@+ && \
+ sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \
+ -e 's|@@REASON@@|NO_PERL=$(NO_PERL)|g' \
+ unimplemented.sh >$@+ && \
+ chmod +x $@+ && \
+ mv $@+ $@
+endif # NO_PERL
configure: configure.ac
$(QUIET_GEN)$(RM) $@ $<+ && \
@@ -1403,6 +1427,7 @@ GIT-BUILD-OPTIONS: .FORCE-GIT-BUILD-OPTIONS
@echo SHELL_PATH=\''$(subst ','\'',$(SHELL_PATH_SQ))'\' >$@
@echo TAR=\''$(subst ','\'',$(subst ','\'',$(TAR)))'\' >>$@
@echo NO_CURL=\''$(subst ','\'',$(subst ','\'',$(NO_CURL)))'\' >>$@
+ @echo NO_PERL=\''$(subst ','\'',$(subst ','\'',$(NO_PERL)))'\' >>$@
### Detect Tck/Tk interpreter path changes
ifndef NO_TCLTK
@@ -1605,9 +1630,11 @@ clean:
$(RM) -r $(GIT_TARNAME) .doc-tmp-dir
$(RM) $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
$(RM) $(htmldocs).tar.gz $(manpages).tar.gz
- $(RM) gitweb/gitweb.cgi
$(MAKE) -C Documentation/ clean
+ifndef NO_PERL
+ $(RM) gitweb/gitweb.cgi
$(MAKE) -C perl clean
+endif
$(MAKE) -C templates/ clean
$(MAKE) -C t/ clean
ifndef NO_TCLTK
diff --git a/bisect.c b/bisect.c
new file mode 100644
index 000000000..58f7e6f77
--- /dev/null
+++ b/bisect.c
@@ -0,0 +1,556 @@
+#include "cache.h"
+#include "commit.h"
+#include "diff.h"
+#include "revision.h"
+#include "refs.h"
+#include "list-objects.h"
+#include "quote.h"
+#include "sha1-lookup.h"
+#include "bisect.h"
+
+static unsigned char (*skipped_sha1)[20];
+static int skipped_sha1_nr;
+static int skipped_sha1_alloc;
+
+static const char **rev_argv;
+static int rev_argv_nr;
+static int rev_argv_alloc;
+
+/* bits #0-15 in revision.h */
+
+#define COUNTED (1u<<16)
+
+/*
+ * This is a truly stupid algorithm, but it's only
+ * used for bisection, and we just don't care enough.
+ *
+ * We care just barely enough to avoid recursing for
+ * non-merge entries.
+ */
+static int count_distance(struct commit_list *entry)
+{
+ int nr = 0;
+
+ while (entry) {
+ struct commit *commit = entry->item;
+ struct commit_list *p;
+
+ if (commit->object.flags & (UNINTERESTING | COUNTED))
+ break;
+ if (!(commit->object.flags & TREESAME))
+ nr++;
+ commit->object.flags |= COUNTED;
+ p = commit->parents;
+ entry = p;
+ if (p) {
+ p = p->next;
+ while (p) {
+ nr += count_distance(p);
+ p = p->next;
+ }
+ }
+ }
+
+ return nr;
+}
+
+static void clear_distance(struct commit_list *list)
+{
+ while (list) {
+ struct commit *commit = list->item;
+ commit->object.flags &= ~COUNTED;
+ list = list->next;
+ }
+}
+
+#define DEBUG_BISECT 0
+
+static inline int weight(struct commit_list *elem)
+{
+ return *((int*)(elem->item->util));
+}
+
+static inline void weight_set(struct commit_list *elem, int weight)
+{
+ *((int*)(elem->item->util)) = weight;
+}
+
+static int count_interesting_parents(struct commit *commit)
+{
+ struct commit_list *p;
+ int count;
+
+ for (count = 0, p = commit->parents; p; p = p->next) {
+ if (p->item->object.flags & UNINTERESTING)
+ continue;
+ count++;
+ }
+ return count;
+}
+
+static inline int halfway(struct commit_list *p, int nr)
+{
+ /*
+ * Don't short-cut something we are not going to return!
+ */
+ if (p->item->object.flags & TREESAME)
+ return 0;
+ if (DEBUG_BISECT)
+ return 0;
+ /*
+ * 2 and 3 are halfway of 5.
+ * 3 is halfway of 6 but 2 and 4 are not.
+ */
+ switch (2 * weight(p) - nr) {
+ case -1: case 0: case 1:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+#if !DEBUG_BISECT
+#define show_list(a,b,c,d) do { ; } while (0)
+#else
+static void show_list(const char *debug, int counted, int nr,
+ struct commit_list *list)
+{
+ struct commit_list *p;
+
+ fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
+
+ for (p = list; p; p = p->next) {
+ struct commit_list *pp;
+ struct commit *commit = p->item;
+ unsigned flags = commit->object.flags;
+ enum object_type type;
+ unsigned long size;
+ char *buf = read_sha1_file(commit->object.sha1, &type, &size);
+ char *ep, *sp;
+
+ fprintf(stderr, "%c%c%c ",
+ (flags & TREESAME) ? ' ' : 'T',
+ (flags & UNINTERESTING) ? 'U' : ' ',
+ (flags & COUNTED) ? 'C' : ' ');
+ if (commit->util)
+ fprintf(stderr, "%3d", weight(p));
+ else
+ fprintf(stderr, "---");
+ fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
+ for (pp = commit->parents; pp; pp = pp->next)
+ fprintf(stderr, " %.*s", 8,
+ sha1_to_hex(pp->item->object.sha1));
+
+ sp = strstr(buf, "\n\n");
+ if (sp) {
+ sp += 2;
+ for (ep = sp; *ep && *ep != '\n'; ep++)
+ ;
+ fprintf(stderr, " %.*s", (int)(ep - sp), sp);
+ }
+ fprintf(stderr, "\n");
+ }
+}
+#endif /* DEBUG_BISECT */
+
+static struct commit_list *best_bisection(struct commit_list *list, int nr)
+{
+ struct commit_list *p, *best;
+ int best_distance = -1;
+
+ best = list;
+ for (p = list; p; p = p->next) {
+ int distance;
+ unsigned flags = p->item->object.flags;
+
+ if (flags & TREESAME)
+ continue;
+ distance = weight(p);
+ if (nr - distance < distance)
+ distance = nr - distance;
+ if (distance > best_distance) {
+ best = p;
+ best_distance = distance;
+ }
+ }
+
+ return best;
+}
+
+struct commit_dist {
+ struct commit *commit;
+ int distance;
+};
+
+static int compare_commit_dist(const void *a_, const void *b_)
+{
+ struct commit_dist *a, *b;
+
+ a = (struct commit_dist *)a_;
+ b = (struct commit_dist *)b_;
+ if (a->distance != b->distance)
+ return b->distance - a->distance; /* desc sort */
+ return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
+}
+
+static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
+{
+ struct commit_list *p;
+ struct commit_dist *array = xcalloc(nr, sizeof(*array));
+ int cnt, i;
+
+ for (p = list, cnt = 0; p; p = p->next) {
+ int distance;
+ unsigned flags = p->item->object.flags;
+
+ if (flags & TREESAME)
+ continue;
+ distance = weight(p);
+ if (nr - distance < distance)
+ distance = nr - distance;
+ array[cnt].commit = p->item;
+ array[cnt].distance = distance;
+ cnt++;
+ }
+ qsort(array, cnt, sizeof(*array), compare_commit_dist);
+ for (p = list, i = 0; i < cnt; i++) {
+ struct name_decoration *r = xmalloc(sizeof(*r) + 100);
+ struct object *obj = &(array[i].commit->object);
+
+ sprintf(r->name, "dist=%d", array[i].distance);
+ r->next = add_decoration(&name_decoration, obj, r);
+ p->item = array[i].commit;
+ p = p->next;
+ }
+ if (p)
+ p->next = NULL;
+ free(array);
+ return list;
+}
+
+/*
+ * zero or positive weight is the number of interesting commits it can
+ * reach, including itself. Especially, weight = 0 means it does not
+ * reach any tree-changing commits (e.g. just above uninteresting one
+ * but traversal is with pathspec).
+ *
+ * weight = -1 means it has one parent and its distance is yet to
+ * be computed.
+ *
+ * weight = -2 means it has more than one parent and its distance is
+ * unknown. After running count_distance() first, they will get zero
+ * or positive distance.
+ */
+static struct commit_list *do_find_bisection(struct commit_list *list,
+ int nr, int *weights,
+ int find_all)
+{
+ int n, counted;
+ struct commit_list *p;
+
+ counted = 0;
+
+ for (n = 0, p = list; p; p = p->next) {
+ struct commit *commit = p->item;
+ unsigned flags = commit->object.flags;
+
+ p->item->util = &weights[n++];
+ switch (count_interesting_parents(commit)) {
+ case 0:
+ if (!(flags & TREESAME)) {
+ weight_set(p, 1);
+ counted++;
+ show_list("bisection 2 count one",
+ counted, nr, list);
+ }
+ /*
+ * otherwise, it is known not to reach any
+ * tree-changing commit and gets weight 0.
+ */
+ break;
+ case 1:
+ weight_set(p, -1);
+ break;
+ default:
+ weight_set(p, -2);
+ break;
+ }
+ }
+
+ show_list("bisection 2 initialize", counted, nr, list);
+
+ /*
+ * If you have only one parent in the resulting set
+ * then you can reach one commit more than that parent
+ * can reach. So we do not have to run the expensive
+ * count_distance() for single strand of pearls.
+ *
+ * However, if you have more than one parents, you cannot
+ * just add their distance and one for yourself, since
+ * they usually reach the same ancestor and you would
+ * end up counting them twice that way.
+ *
+ * So we will first count distance of merges the usual
+ * way, and then fill the blanks using cheaper algorithm.
+ */
+ for (p = list; p; p = p->next) {
+ if (p->item->object.flags & UNINTERESTING)
+ continue;
+ if (weight(p) != -2)
+ continue;
+ weight_set(p, count_distance(p));
+ clear_distance(list);
+
+ /* Does it happen to be at exactly half-way? */
+ if (!find_all && halfway(p, nr))
+ return p;
+ counted++;
+ }
+
+ show_list("bisection 2 count_distance", counted, nr, list);
+
+ while (counted < nr) {
+ for (p = list; p; p = p->next) {
+ struct commit_list *q;
+ unsigned flags = p->item->object.flags;
+
+ if (0 <= weight(p))
+ continue;
+ for (q = p->item->parents; q; q = q->next) {
+ if (q->item->object.flags & UNINTERESTING)
+ continue;
+ if (0 <= weight(q))
+ break;
+ }
+ if (!q)
+ continue;
+
+ /*
+ * weight for p is unknown but q is known.
+ * add one for p itself if p is to be counted,
+ * otherwise inherit it from q directly.
+ */
+ if (!(flags & TREESAME)) {
+ weight_set(p, weight(q)+1);
+ counted++;
+ show_list("bisection 2 count one",
+ counted, nr, list);
+ }
+ else
+ weight_set(p, weight(q));
+
+ /* Does it happen to be at exactly half-way? */
+ if (!find_all && halfway(p, nr))
+ return p;
+ }
+ }
+
+ show_list("bisection 2 counted all", counted, nr, list);
+
+ if (!find_all)
+ return best_bisection(list, nr);
+ else
+ return best_bisection_sorted(list, nr);
+}
+
+struct commit_list *find_bisection(struct commit_list *list,
+ int *reaches, int *all,
+ int find_all)
+{
+ int nr, on_list;
+ struct commit_list *p, *best, *next, *last;
+ int *weights;
+
+ show_list("bisection 2 entry", 0, 0, list);
+
+ /*
+ * Count the number of total and tree-changing items on the
+ * list, while reversing the list.
+ */
+ for (nr = on_list = 0, last = NULL, p = list;
+ p;
+ p = next) {
+ unsigned flags = p->item->object.flags;
+
+ next = p->next;
+ if (flags & UNINTERESTING)
+ continue;
+ p->next = last;
+ last = p;
+ if (!(flags & TREESAME))
+ nr++;
+ on_list++;
+ }
+ list = last;
+ show_list("bisection 2 sorted", 0, nr, list);
+
+ *all = nr;
+ weights = xcalloc(on_list, sizeof(*weights));
+
+ /* Do the real work of finding bisection commit. */
+ best = do_find_bisection(list, nr, weights, find_all);
+ if (best) {
+ if (!find_all)
+ best->next = NULL;
+ *reaches = weight(best);
+ }
+ free(weights);
+ return best;
+}
+
+static int register_ref(const char *refname, const unsigned char *sha1,
+ int flags, void *cb_data)
+{
+ if (!strcmp(refname, "bad")) {
+ ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+ rev_argv[rev_argv_nr++] = xstrdup(sha1_to_hex(sha1));
+ } else if (!prefixcmp(refname, "good-")) {
+ const char *hex = sha1_to_hex(sha1);
+ char *good = xmalloc(strlen(hex) + 2);
+ *good = '^';
+ memcpy(good + 1, hex, strlen(hex) + 1);
+ ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+ rev_argv[rev_argv_nr++] = good;
+ } else if (!prefixcmp(refname, "skip-")) {
+ ALLOC_GROW(skipped_sha1, skipped_sha1_nr + 1,
+ skipped_sha1_alloc);
+ hashcpy(skipped_sha1[skipped_sha1_nr++], sha1);
+ }
+
+ return 0;
+}
+
+static int read_bisect_refs(void)
+{
+ return for_each_ref_in("refs/bisect/", register_ref, NULL);
+}
+
+void read_bisect_paths(void)
+{
+ struct strbuf str = STRBUF_INIT;
+ const char *filename = git_path("BISECT_NAMES");
+ FILE *fp = fopen(filename, "r");
+
+ if (!fp)
+ die("Could not open file '%s': %s", filename, strerror(errno));
+
+ while (strbuf_getline(&str, fp, '\n') != EOF) {
+ char *quoted;
+ int res;
+
+ strbuf_trim(&str);
+ quoted = strbuf_detach(&str, NULL);
+ res = sq_dequote_to_argv(quoted, &rev_argv,
+ &rev_argv_nr, &rev_argv_alloc);
+ if (res)
+ die("Badly quoted content in file '%s': %s",
+ filename, quoted);
+ }
+
+ strbuf_release(&str);
+ fclose(fp);
+}
+
+static int skipcmp(const void *a, const void *b)
+{
+ return hashcmp(a, b);
+}
+
+static void prepare_skipped(void)
+{
+ qsort(skipped_sha1, skipped_sha1_nr, sizeof(*skipped_sha1), skipcmp);
+}
+
+static const unsigned char *skipped_sha1_access(size_t index, void *table)
+{
+ unsigned char (*skipped)[20] = table;
+ return skipped[index];
+}
+
+static int lookup_skipped(unsigned char *sha1)
+{
+ return sha1_pos(sha1, skipped_sha1, skipped_sha1_nr,
+ skipped_sha1_access);
+}
+
+struct commit_list *filter_skipped(struct commit_list *list,
+ struct commit_list **tried,
+ int show_all)
+{
+ struct commit_list *filtered = NULL, **f = &filtered;
+
+ *tried = NULL;
+
+ if (!skipped_sha1_nr)
+ return list;
+
+ prepare_skipped();
+
+ while (list) {
+ struct commit_list *next = list->next;
+ list->next = NULL;
+ if (0 <= lookup_skipped(list->item->object.sha1)) {
+ /* Move current to tried list */
+ *tried = list;
+ tried = &list->next;
+ } else {
+ if (!show_all)
+ return list;
+ /* Move current to filtered list */
+ *f = list;
+ f = &list->next;
+ }
+ list = next;
+ }
+
+ return filtered;
+}
+
+static void bisect_rev_setup(struct rev_info *revs, const char *prefix)
+{
+ init_revisions(revs, prefix);
+ revs->abbrev = 0;
+ revs->commit_format = CMIT_FMT_UNSPECIFIED;
+
+ /* argv[0] will be ignored by setup_revisions */
+ ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+ rev_argv[rev_argv_nr++] = xstrdup("bisect_rev_setup");
+
+ if (read_bisect_refs())
+ die("reading bisect refs failed");
+
+ ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+ rev_argv[rev_argv_nr++] = xstrdup("--");
+
+ read_bisect_paths();
+
+ ALLOC_GROW(rev_argv, rev_argv_nr + 1, rev_argv_alloc);
+ rev_argv[rev_argv_nr++] = NULL;
+
+ setup_revisions(rev_argv_nr, rev_argv, revs, NULL);
+
+ revs->limited = 1;
+}
+
+int bisect_next_vars(const char *prefix)
+{
+ struct rev_info revs;
+ struct rev_list_info info;
+ int reaches = 0, all = 0;
+
+ memset(&info, 0, sizeof(info));
+ info.revs = &revs;
+ info.bisect_show_flags = BISECT_SHOW_TRIED | BISECT_SHOW_STRINGED;
+
+ bisect_rev_setup(&revs, prefix);
+
+ if (prepare_revision_walk(&revs))
+ die("revision walk setup failed");
+ if (revs.tree_objects)
+ mark_edges_uninteresting(revs.commits, &revs, NULL);
+
+ revs.commits = find_bisection(revs.commits, &reaches, &all,
+ !!skipped_sha1_nr);
+
+ return show_bisect_vars(&info, reaches, all);
+}
diff --git a/bisect.h b/bisect.h
new file mode 100644
index 000000000..fdba91387
--- /dev/null
+++ b/bisect.h
@@ -0,0 +1,29 @@
+#ifndef BISECT_H
+#define BISECT_H
+
+extern struct commit_list *find_bisection(struct commit_list *list,
+ int *reaches, int *all,
+ int find_all);
+
+extern struct commit_list *filter_skipped(struct commit_list *list,
+ struct commit_list **tried,
+ int show_all);
+
+/* bisect_show_flags flags in struct rev_list_info */
+#define BISECT_SHOW_ALL (1<<0)
+#define BISECT_SHOW_TRIED (1<<1)
+#define BISECT_SHOW_STRINGED (1<<2)
+
+struct rev_list_info {
+ struct rev_info *revs;
+ int bisect_show_flags;
+ int show_timestamp;
+ int hdr_termination;
+ const char *header_prefix;
+};
+
+extern int show_bisect_vars(struct rev_list_info *info, int reaches, int all);
+
+extern int bisect_next_vars(const char *prefix);
+
+#endif
diff --git a/builtin-bisect--helper.c b/builtin-bisect--helper.c
new file mode 100644
index 000000000..8fe778766
--- /dev/null
+++ b/builtin-bisect--helper.c
@@ -0,0 +1,27 @@
+#include "builtin.h"
+#include "cache.h"
+#include "parse-options.h"
+#include "bisect.h"
+
+static const char * const git_bisect_helper_usage[] = {
+ "git bisect--helper --next-vars",
+ NULL
+};
+
+int cmd_bisect__helper(int argc, const char **argv, const char *prefix)
+{
+ int next_vars = 0;
+ struct option options[] = {
+ OPT_BOOLEAN(0, "next-vars", &next_vars,
+ "output next bisect step variables"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, options, git_bisect_helper_usage, 0);
+
+ if (!next_vars)
+ usage_with_options(git_bisect_helper_usage, options);
+
+ /* next-vars */
+ return bisect_next_vars(prefix);
+}
diff --git a/builtin-branch.c b/builtin-branch.c
index ca81d725c..327582169 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -301,19 +301,30 @@ static int ref_cmp(const void *r1, const void *r2)
return strcmp(c1->name, c2->name);
}
-static void fill_tracking_info(struct strbuf *stat, const char *branch_name)
+static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
+ int show_upstream_ref)
{
int ours, theirs;
struct branch *branch = branch_get(branch_name);
- if (!stat_tracking_info(branch, &ours, &theirs) || (!ours && !theirs))
+ if (!stat_tracking_info(branch, &ours, &theirs)) {
+ if (branch && branch->merge && branch->merge[0]->dst &&
+ show_upstream_ref)
+ strbuf_addf(stat, "[%s] ",
+ shorten_unambiguous_ref(branch->merge[0]->dst));
return;
+ }
+
+ strbuf_addch(stat, '[');
+ if (show_upstream_ref)
+ strbuf_addf(stat, "%s: ",
+ shorten_unambiguous_ref(branch->merge[0]->dst));
if (!ours)
- strbuf_addf(stat, "[behind %d] ", theirs);
+ strbuf_addf(stat, "behind %d] ", theirs);
else if (!theirs)
- strbuf_addf(stat, "[ahead %d] ", ours);
+ strbuf_addf(stat, "ahead %d] ", ours);
else
- strbuf_addf(stat, "[ahead %d, behind %d] ", ours, theirs);
+ strbuf_addf(stat, "ahead %d, behind %d] ", ours, theirs);
}
static int matches_merge_filter(struct commit *commit)
@@ -379,7 +390,7 @@ static void print_ref_item(struct ref_item *item, int maxwidth, int verbose,
}
if (item->kind == REF_LOCAL_BRANCH)
- fill_tracking_info(&stat, item->name);
+ fill_tracking_info(&stat, item->name, verbose > 1);
strbuf_addf(&out, " %s %s%s",
find_unique_abbrev(item->commit->object.sha1, abbrev),
diff --git a/builtin-checkout.c b/builtin-checkout.c
index 33d1fecb6..ee1edd406 100644
--- a/builtin-checkout.c
+++ b/builtin-checkout.c
@@ -53,9 +53,6 @@ static int update_some(const unsigned char *sha1, const char *base, int baselen,
int len;
struct cache_entry *ce;
- if (S_ISGITLINK(mode))
- return 0;
-
if (S_ISDIR(mode))
return READ_TREE_RECURSIVE;
@@ -498,10 +495,10 @@ static void update_refs_for_switch(struct checkout_opts *opts,
create_symref("HEAD", new->path, msg.buf);
if (!opts->quiet) {
if (old->path && !strcmp(new->path, old->path))
- fprintf(stderr, "Already on \"%s\"\n",
+ fprintf(stderr, "Already on '%s'\n",
new->name);
else
- fprintf(stderr, "Switched to%s branch \"%s\"\n",
+ fprintf(stderr, "Switched to%s branch '%s'\n",
opts->new_branch ? " a new" : "",
new->name);
}
@@ -510,7 +507,7 @@ static void update_refs_for_switch(struct checkout_opts *opts,
REF_NODEREF, DIE_ON_ERR);
if (!opts->quiet) {
if (old->path)
- fprintf(stderr, "Note: moving to \"%s\" which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b <new_branch_name>\n", new->name);
+ fprintf(stderr, "Note: moving to '%s' which isn't a local branch\nIf you want to create a new branch from this checkout, you may do so\n(now or later) by using -b with the checkout command again. Example:\n git checkout -b <new_branch_name>\n", new->name);
describe_detached_head("HEAD is now at", new->commit);
}
}
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index 5cbb4b081..c8114c8af 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -8,6 +8,7 @@
#include "blob.h"
#include "quote.h"
#include "parse-options.h"
+#include "remote.h"
/* Quoting styles */
#define QUOTE_NONE 0
@@ -66,6 +67,7 @@ static struct {
{ "subject" },
{ "body" },
{ "contents" },
+ { "upstream" },
};
/*
@@ -544,109 +546,6 @@ static void grab_values(struct atom_value *val, int deref, struct object *obj, v
}
/*
- * generate a format suitable for scanf from a ref_rev_parse_rules
- * rule, that is replace the "%.*s" spec with a "%s" spec
- */
-static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
-{
- char *spec;
-
- spec = strstr(rule, "%.*s");
- if (!spec || strstr(spec + 4, "%.*s"))
- die("invalid rule in ref_rev_parse_rules: %s", rule);
-
- /* copy all until spec */
- strncpy(scanf_fmt, rule, spec - rule);
- scanf_fmt[spec - rule] = '\0';
- /* copy new spec */
- strcat(scanf_fmt, "%s");
- /* copy remaining rule */
- strcat(scanf_fmt, spec + 4);
-
- return;
-}
-
-/*
- * Shorten the refname to an non-ambiguous form
- */
-static char *get_short_ref(struct refinfo *ref)
-{
- int i;
- static char **scanf_fmts;
- static int nr_rules;
- char *short_name;
-
- /* pre generate scanf formats from ref_rev_parse_rules[] */
- if (!nr_rules) {
- size_t total_len = 0;
-
- /* the rule list is NULL terminated, count them first */
- for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
- /* no +1 because strlen("%s") < strlen("%.*s") */
- total_len += strlen(ref_rev_parse_rules[nr_rules]);
-
- scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
-
- total_len = 0;
- for (i = 0; i < nr_rules; i++) {
- scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
- + total_len;
- gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
- total_len += strlen(ref_rev_parse_rules[i]);
- }
- }
-
- /* bail out if there are no rules */
- if (!nr_rules)
- return ref->refname;
-
- /* buffer for scanf result, at most ref->refname must fit */
- short_name = xstrdup(ref->refname);
-
- /* skip first rule, it will always match */
- for (i = nr_rules - 1; i > 0 ; --i) {
- int j;
- int short_name_len;
-
- if (1 != sscanf(ref->refname, scanf_fmts[i], short_name))
- continue;
-
- short_name_len = strlen(short_name);
-
- /*
- * check if the short name resolves to a valid ref,
- * but use only rules prior to the matched one
- */
- for (j = 0; j < i; j++) {
- const char *rule = ref_rev_parse_rules[j];
- unsigned char short_objectname[20];
- char refname[PATH_MAX];
-
- /*
- * the short name is ambiguous, if it resolves
- * (with this previous rule) to a valid ref
- * read_ref() returns 0 on success
- */
- mksnpath(refname, sizeof(refname),
- rule, short_name_len, short_name);
- if (!read_ref(refname, short_objectname))
- break;
- }
-
- /*
- * short name is non-ambiguous if all previous rules
- * haven't resolved to a valid ref
- */
- if (j == i)
- return short_name;
- }
-
- free(short_name);
- return ref->refname;
-}
-
-
-/*
* Parse the object referred by ref, and grab needed value.
*/
static void populate_value(struct refinfo *ref)
@@ -672,32 +571,49 @@ static void populate_value(struct refinfo *ref)
const char *name = used_atom[i];
struct atom_value *v = &ref->value[i];
int deref = 0;
+ const char *refname;
+ const char *formatp;
+
if (*name == '*') {
deref = 1;
name++;
}
- if (!prefixcmp(name, "refname")) {
- const char *formatp = strchr(name, ':');
- const char *refname = ref->refname;
-
- /* look for "short" refname format */
- if (formatp) {
- formatp++;
- if (!strcmp(formatp, "short"))
- refname = get_short_ref(ref);
- else
- die("unknown refname format %s",
- formatp);
- }
- if (!deref)
- v->s = refname;
- else {
- int len = strlen(refname);
- char *s = xmalloc(len + 4);
- sprintf(s, "%s^{}", refname);
- v->s = s;
- }
+ if (!prefixcmp(name, "refname"))
+ refname = ref->refname;
+ else if(!prefixcmp(name, "upstream")) {
+ struct branch *branch;
+ /* only local branches may have an upstream */
+ if (prefixcmp(ref->refname, "refs/heads/"))
+ continue;
+ branch = branch_get(ref->refname + 11);
+
+ if (!branch || !branch->merge || !branch->merge[0] ||
+ !branch->merge[0]->dst)
+ continue;
+ refname = branch->merge[0]->dst;
+ }
+ else
+ continue;
+
+ formatp = strchr(name, ':');
+ /* look for "short" refname format */
+ if (formatp) {
+ formatp++;
+ if (!strcmp(formatp, "short"))
+ refname = shorten_unambiguous_ref(refname);
+ else
+ die("unknown %.*s format %s",
+ (int)(formatp - name), name, formatp);
+ }
+
+ if (!deref)
+ v->s = refname;
+ else {
+ int len = strlen(refname);
+ char *s = xmalloc(len + 4);
+ sprintf(s, "%s^{}", refname);
+ v->s = s;
}
}
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 9fc3b3554..d3360ac1f 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -1612,7 +1612,7 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size,
return;
}
if (progress > pack_to_stdout)
- fprintf(stderr, "Delta compression using %d threads.\n",
+ fprintf(stderr, "Delta compression using up to %d threads.\n",
delta_search_threads);
/* Partition the work amongst work threads. */
@@ -1901,17 +1901,19 @@ static void read_object_list_from_stdin(void)
#define OBJECT_ADDED (1u<<20)
-static void show_commit(struct commit *commit)
+static void show_commit(struct commit *commit, void *data)
{
add_object_entry(commit->object.sha1, OBJ_COMMIT, NULL, 0);
commit->object.flags |= OBJECT_ADDED;
}
-static void show_object(struct object_array_entry *p)
+static void show_object(struct object_array_entry *p, void *data)
{
add_preferred_base_object(p->name);
add_object_entry(p->item->sha1, p->item->type, p->name, 0);
p->item->flags |= OBJECT_ADDED;
+ free((char *)p->name);
+ p->name = NULL;
}
static void show_edge(struct commit *commit)
@@ -2071,7 +2073,7 @@ static void get_object_list(int ac, const char **av)
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
- traverse_commit_list(&revs, show_commit, show_object);
+ traverse_commit_list(&revs, show_commit, show_object, NULL);
if (keep_unreachable)
add_objects_in_unpacked_packs(&revs);
diff --git a/builtin-reflog.c b/builtin-reflog.c
index d95f515f2..249ad2a31 100644
--- a/builtin-reflog.c
+++ b/builtin-reflog.c
@@ -52,6 +52,7 @@ struct collect_reflog_cb {
#define INCOMPLETE (1u<<10)
#define STUDYING (1u<<11)
+#define REACHABLE (1u<<12)
static int tree_is_complete(const unsigned char *sha1)
{
@@ -209,6 +210,70 @@ static int keep_entry(struct commit **it, unsigned char *sha1)
return 1;
}
+static int unreachable(struct expire_reflog_cb *cb, struct commit *commit, unsigned char *sha1)
+{
+ /*
+ * We may or may not have the commit yet - if not, look it
+ * up using the supplied sha1.
+ */
+ if (!commit) {
+ if (is_null_sha1(sha1))
+ return 0;
+
+ commit = lookup_commit_reference_gently(sha1, 1);
+
+ /* Not a commit -- keep it */
+ if (!commit)
+ return 0;
+ }
+
+ /* Reachable from the current ref? Don't prune. */
+ if (commit->object.flags & REACHABLE)
+ return 0;
+ if (in_merge_bases(commit, &cb->ref_commit, 1))
+ return 0;
+
+ /* We can't reach it - prune it. */
+ return 1;
+}
+
+static void mark_reachable(struct commit *commit, unsigned long expire_limit)
+{
+ /*
+ * We need to compute if commit on either side of an reflog
+ * entry is reachable from the tip of the ref for all entries.
+ * Mark commits that are reachable from the tip down to the
+ * time threashold first; we know a commit marked thusly is
+ * reachable from the tip without running in_merge_bases()
+ * at all.
+ */
+ struct commit_list *pending = NULL;
+
+ commit_list_insert(commit, &pending);
+ while (pending) {
+ struct commit_list *entry = pending;
+ struct commit_list *parent;
+ pending = entry->next;
+ commit = entry->item;
+ free(entry);
+ if (commit->object.flags & REACHABLE)
+ continue;
+ if (parse_commit(commit))
+ continue;
+ commit->object.flags |= REACHABLE;
+ if (commit->date < expire_limit)
+ continue;
+ parent = commit->parents;
+ while (parent) {
+ commit = parent->item;
+ parent = parent->next;
+ if (commit->object.flags & REACHABLE)
+ continue;
+ commit_list_insert(commit, &pending);
+ }
+ }
+}
+
static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
const char *email, unsigned long timestamp, int tz,
const char *message, void *cb_data)
@@ -230,12 +295,7 @@ static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
if (timestamp < cb->cmd->expire_unreachable) {
if (!cb->ref_commit)
goto prune;
- if (!old && !is_null_sha1(osha1))
- old = lookup_commit_reference_gently(osha1, 1);
- if (!new && !is_null_sha1(nsha1))
- new = lookup_commit_reference_gently(nsha1, 1);
- if ((old && !in_merge_bases(old, &cb->ref_commit, 1)) ||
- (new && !in_merge_bases(new, &cb->ref_commit, 1)))
+ if (unreachable(cb, old, osha1) || unreachable(cb, new, nsha1))
goto prune;
}
@@ -288,7 +348,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
cb.ref_commit = lookup_commit_reference_gently(sha1, 1);
cb.ref = ref;
cb.cmd = cmd;
+ if (cb.ref_commit)
+ mark_reachable(cb.ref_commit, cmd->expire_total);
for_each_reflog_ent(ref, expire_reflog_ent, &cb);
+ if (cb.ref_commit)
+ clear_commit_marks(cb.ref_commit, REACHABLE);
finish:
if (cb.newlog) {
if (fclose(cb.newlog)) {
diff --git a/builtin-remote.c b/builtin-remote.c
index 9ef846f6a..ca7c639ad 100644
--- a/builtin-remote.c
+++ b/builtin-remote.c
@@ -15,7 +15,7 @@ static const char * const builtin_remote_usage[] = {
"git remote set-head <name> [-a | -d | <branch>]",
"git remote show [-n] <name>",
"git remote prune [-n | --dry-run] <name>",
- "git remote [-v | --verbose] update [group]",
+ "git remote [-v | --verbose] update [-p | --prune] [group]",
NULL
};
@@ -26,6 +26,7 @@ static const char * const builtin_remote_usage[] = {
static int verbose;
static int show_all(void);
+static int prune_remote(const char *remote, int dry_run);
static inline int postfixcmp(const char *string, const char *postfix)
{
@@ -1128,46 +1129,49 @@ static int prune(int argc, const char **argv)
OPT__DRY_RUN(&dry_run),
OPT_END()
};
- struct ref_states states;
- const char *dangling_msg;
argc = parse_options(argc, argv, options, builtin_remote_usage, 0);
if (argc < 1)
usage_with_options(builtin_remote_usage, options);
- dangling_msg = (dry_run
- ? " %s will become dangling!\n"
- : " %s has become dangling!\n");
-
- memset(&states, 0, sizeof(states));
- for (; argc; argc--, argv++) {
- int i;
+ for (; argc; argc--, argv++)
+ result |= prune_remote(*argv, dry_run);
- get_remote_ref_states(*argv, &states, GET_REF_STATES);
+ return result;
+}
- if (states.stale.nr) {
- printf("Pruning %s\n", *argv);
- printf("URL: %s\n",
- states.remote->url_nr
- ? states.remote->url[0]
- : "(no URL)");
- }
+static int prune_remote(const char *remote, int dry_run)
+{
+ int result = 0, i;
+ struct ref_states states;
+ const char *dangling_msg = dry_run
+ ? " %s will become dangling!\n"
+ : " %s has become dangling!\n";
- for (i = 0; i < states.stale.nr; i++) {
- const char *refname = states.stale.items[i].util;
+ memset(&states, 0, sizeof(states));
+ get_remote_ref_states(remote, &states, GET_REF_STATES);
+
+ if (states.stale.nr) {
+ printf("Pruning %s\n", remote);
+ printf("URL: %s\n",
+ states.remote->url_nr
+ ? states.remote->url[0]
+ : "(no URL)");
+ }
- if (!dry_run)
- result |= delete_ref(refname, NULL, 0);
+ for (i = 0; i < states.stale.nr; i++) {
+ const char *refname = states.stale.items[i].util;
- printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
- abbrev_ref(refname, "refs/remotes/"));
- warn_dangling_symref(dangling_msg, refname);
- }
+ if (!dry_run)
+ result |= delete_ref(refname, NULL, 0);
- free_remote_ref_states(&states);
+ printf(" * [%s] %s\n", dry_run ? "would prune" : "pruned",
+ abbrev_ref(refname, "refs/remotes/"));
+ warn_dangling_symref(dangling_msg, refname);
}
+ free_remote_ref_states(&states);
return result;
}
@@ -1184,16 +1188,18 @@ struct remote_group {
struct string_list *list;
} remote_group;
-static int get_remote_group(const char *key, const char *value, void *cb)
+static int get_remote_group(const char *key, const char *value, void *num_hits)
{
if (!prefixcmp(key, "remotes.") &&
!strcmp(key + 8, remote_group.name)) {
/* split list by white space */
int space = strcspn(value, " \t\n");
while (*value) {
- if (space > 1)
+ if (space > 1) {
string_list_append(xstrndup(value, space),
remote_group.list);
+ ++*((int *)num_hits);
+ }
value += space + (value[space] != '\0');
space = strcspn(value, " \t\n");
}
@@ -1204,10 +1210,18 @@ static int get_remote_group(const char *key, const char *value, void *cb)
static int update(int argc, const char **argv)
{
- int i, result = 0;
+ int i, result = 0, prune = 0;
struct string_list list = { NULL, 0, 0, 0 };
static const char *default_argv[] = { NULL, "default", NULL };
+ struct option options[] = {
+ OPT_GROUP("update specific options"),
+ OPT_BOOLEAN('p', "prune", &prune,
+ "prune remotes after fecthing"),
+ OPT_END()
+ };
+ argc = parse_options(argc, argv, options, builtin_remote_usage,
+ PARSE_OPT_KEEP_ARGV0);
if (argc < 2) {
argc = 2;
argv = default_argv;
@@ -1215,15 +1229,28 @@ static int update(int argc, const char **argv)
remote_group.list = &list;
for (i = 1; i < argc; i++) {
+ int groups_found = 0;
remote_group.name = argv[i];
- result = git_config(get_remote_group, NULL);
+ result = git_config(get_remote_group, &groups_found);
+ if (!groups_found && (i != 1 || strcmp(argv[1], "default"))) {
+ struct remote *remote;
+ if (!remote_is_configured(argv[i]))
+ die("No such remote or remote group: %s",
+ argv[i]);
+ remote = remote_get(argv[i]);
+ string_list_append(remote->name, remote_group.list);
+ }
}
if (!result && !list.nr && argc == 2 && !strcmp(argv[1], "default"))
result = for_each_remote(get_one_remote_for_update, &list);
- for (i = 0; i < list.nr; i++)
- result |= fetch_remote(list.items[i].string);
+ for (i = 0; i < list.nr; i++) {
+ int err = fetch_remote(list.items[i].string);
+ result |= err;
+ if (!err && prune)
+ result |= prune_remote(list.items[i].string, 0);
+ }
/* all names were strdup()ed or strndup()ed */
list.strdup_strings = 1;
diff --git a/builtin-rev-list.c b/builtin-rev-list.c
index 40d5fcb6b..193993cf4 100644
--- a/builtin-rev-list.c
+++ b/builtin-rev-list.c
@@ -1,20 +1,12 @@
#include "cache.h"
-#include "refs.h"
-#include "tag.h"
#include "commit.h"
-#include "tree.h"
-#include "blob.h"
-#include "tree-walk.h"
#include "diff.h"
#include "revision.h"
#include "list-objects.h"
#include "builtin.h"
#include "log-tree.h"
#include "graph.h"
-
-/* bits #0-15 in revision.h */
-
-#define COUNTED (1u<<16)
+#include "bisect.h"
static const char rev_list_usage[] =
"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n"
@@ -50,73 +42,69 @@ static const char rev_list_usage[] =
" --bisect-all"
;
-static struct rev_info revs;
-
-static int bisect_list;
-static int show_timestamp;
-static int hdr_termination;
-static const char *header_prefix;
-
-static void finish_commit(struct commit *commit);
-static void show_commit(struct commit *commit)
+static void finish_commit(struct commit *commit, void *data);
+static void show_commit(struct commit *commit, void *data)
{
- graph_show_commit(revs.graph);
+ struct rev_list_info *info = data;
+ struct rev_info *revs = info->revs;
+
+ graph_show_commit(revs->graph);
- if (show_timestamp)
+ if (info->show_timestamp)
printf("%lu ", commit->date);
- if (header_prefix)
- fputs(header_prefix, stdout);
+ if (info->header_prefix)
+ fputs(info->header_prefix, stdout);
- if (!revs.graph) {
+ if (!revs->graph) {
if (commit->object.flags & BOUNDARY)
putchar('-');
else if (commit->object.flags & UNINTERESTING)
putchar('^');
- else if (revs.left_right) {
+ else if (revs->left_right) {
if (commit->object.flags & SYMMETRIC_LEFT)
putchar('<');
else
putchar('>');
}
}
- if (revs.abbrev_commit && revs.abbrev)
- fputs(find_unique_abbrev(commit->object.sha1, revs.abbrev),
+ if (revs->abbrev_commit && revs->abbrev)
+ fputs(find_unique_abbrev(commit->object.sha1, revs->abbrev),
stdout);
else
fputs(sha1_to_hex(commit->object.sha1), stdout);
- if (revs.print_parents) {
+ if (revs->print_parents) {
struct commit_list *parents = commit->parents;
while (parents) {
printf(" %s", sha1_to_hex(parents->item->object.sha1));
parents = parents->next;
}
}
- if (revs.children.name) {
+ if (revs->children.name) {
struct commit_list *children;
- children = lookup_decoration(&revs.children, &commit->object);
+ children = lookup_decoration(&revs->children, &commit->object);
while (children) {
printf(" %s", sha1_to_hex(children->item->object.sha1));
children = children->next;
}
}
- show_decorations(&revs, commit);
- if (revs.commit_format == CMIT_FMT_ONELINE)
+ show_decorations(revs, commit);
+ if (revs->commit_format == CMIT_FMT_ONELINE)
putchar(' ');
else
putchar('\n');
- if (revs.verbose_header && commit->buffer) {
+ if (revs->verbose_header && commit->buffer) {
struct strbuf buf = STRBUF_INIT;
- pretty_print_commit(revs.commit_format, commit,
- &buf, revs.abbrev, NULL, NULL,
- revs.date_mode, 0);
- if (revs.graph) {
+ pretty_print_commit(revs->commit_format, commit,
+ &buf, revs->abbrev, NULL, NULL,
+ revs->date_mode, 0);
+ if (revs->graph) {
if (buf.len) {
- if (revs.commit_format != CMIT_FMT_ONELINE)
- graph_show_oneline(revs.graph);
+ if (revs->commit_format != CMIT_FMT_ONELINE)
+ graph_show_oneline(revs->graph);
- graph_show_commit_msg(revs.graph, &buf);
+ graph_show_commit_msg(revs->graph, &buf);
/*
* Add a newline after the commit message.
@@ -134,7 +122,7 @@ static void show_commit(struct commit *commit)
* format doesn't explicitly end in a newline.)
*/
if (buf.len && buf.buf[buf.len - 1] == '\n')
- graph_show_padding(revs.graph);
+ graph_show_padding(revs->graph);
putchar('\n');
} else {
/*
@@ -142,23 +130,23 @@ static void show_commit(struct commit *commit)
* the rest of the graph output for this
* commit.
*/
- if (graph_show_remainder(revs.graph))
+ if (graph_show_remainder(revs->graph))
putchar('\n');
}
} else {
if (buf.len)
- printf("%s%c", buf.buf, hdr_termination);
+ printf("%s%c", buf.buf, info->hdr_termination);
}
strbuf_release(&buf);
} else {
- if (graph_show_remainder(revs.graph))
+ if (graph_show_remainder(revs->graph))
putchar('\n');
}
maybe_flush_or_die(stdout, "stdout");
- finish_commit(commit);
+ finish_commit(commit, data);
}
-static void finish_commit(struct commit *commit)
+static void finish_commit(struct commit *commit, void *data)
{
if (commit->parents) {
free_commit_list(commit->parents);
@@ -168,20 +156,20 @@ static void finish_commit(struct commit *commit)
commit->buffer = NULL;
}
-static void finish_object(struct object_array_entry *p)
+static void finish_object(struct object_array_entry *p, void *data)
{
if (p->item->type == OBJ_BLOB && !has_sha1_file(p->item->sha1))
die("missing blob object '%s'", sha1_to_hex(p->item->sha1));
}
-static void show_object(struct object_array_entry *p)
+static void show_object(struct object_array_entry *p, void *data)
{
/* An object with name "foo\n0000000..." can be used to
* confuse downstream "git pack-objects" very badly.
*/
const char *ep = strchr(p->name, '\n');
- finish_object(p);
+ finish_object(p, data);
if (ep) {
printf("%s %.*s\n", sha1_to_hex(p->item->sha1),
(int) (ep - p->name),
@@ -196,384 +184,6 @@ static void show_edge(struct commit *commit)
printf("-%s\n", sha1_to_hex(commit->object.sha1));
}
-/*
- * This is a truly stupid algorithm, but it's only
- * used for bisection, and we just don't care enough.
- *
- * We care just barely enough to avoid recursing for
- * non-merge entries.
- */
-static int count_distance(struct commit_list *entry)
-{
- int nr = 0;
-
- while (entry) {
- struct commit *commit = entry->item;
- struct commit_list *p;
-
- if (commit->object.flags & (UNINTERESTING | COUNTED))
- break;
- if (!(commit->object.flags & TREESAME))
- nr++;
- commit->object.flags |= COUNTED;
- p = commit->parents;
- entry = p;
- if (p) {
- p = p->next;
- while (p) {
- nr += count_distance(p);
- p = p->next;
- }
- }
- }
-
- return nr;
-}
-
-static void clear_distance(struct commit_list *list)
-{
- while (list) {
- struct commit *commit = list->item;
- commit->object.flags &= ~COUNTED;
- list = list->next;
- }
-}
-
-#define DEBUG_BISECT 0
-
-static inline int weight(struct commit_list *elem)
-{
- return *((int*)(elem->item->util));
-}
-
-static inline void weight_set(struct commit_list *elem, int weight)
-{
- *((int*)(elem->item->util)) = weight;
-}
-
-static int count_interesting_parents(struct commit *commit)
-{
- struct commit_list *p;
- int count;
-
- for (count = 0, p = commit->parents; p; p = p->next) {
- if (p->item->object.flags & UNINTERESTING)
- continue;
- count++;
- }
- return count;
-}
-
-static inline int halfway(struct commit_list *p, int nr)
-{
- /*
- * Don't short-cut something we are not going to return!
- */
- if (p->item->object.flags & TREESAME)
- return 0;
- if (DEBUG_BISECT)
- return 0;
- /*
- * 2 and 3 are halfway of 5.
- * 3 is halfway of 6 but 2 and 4 are not.
- */
- switch (2 * weight(p) - nr) {
- case -1: case 0: case 1:
- return 1;
- default:
- return 0;
- }
-}
-
-#if !DEBUG_BISECT
-#define show_list(a,b,c,d) do { ; } while (0)
-#else
-static void show_list(const char *debug, int counted, int nr,
- struct commit_list *list)
-{
- struct commit_list *p;
-
- fprintf(stderr, "%s (%d/%d)\n", debug, counted, nr);
-
- for (p = list; p; p = p->next) {
- struct commit_list *pp;
- struct commit *commit = p->item;
- unsigned flags = commit->object.flags;
- enum object_type type;
- unsigned long size;
- char *buf = read_sha1_file(commit->object.sha1, &type, &size);
- char *ep, *sp;
-
- fprintf(stderr, "%c%c%c ",
- (flags & TREESAME) ? ' ' : 'T',
- (flags & UNINTERESTING) ? 'U' : ' ',
- (flags & COUNTED) ? 'C' : ' ');
- if (commit->util)
- fprintf(stderr, "%3d", weight(p));
- else
- fprintf(stderr, "---");
- fprintf(stderr, " %.*s", 8, sha1_to_hex(commit->object.sha1));
- for (pp = commit->parents; pp; pp = pp->next)
- fprintf(stderr, " %.*s", 8,
- sha1_to_hex(pp->item->object.sha1));
-
- sp = strstr(buf, "\n\n");
- if (sp) {
- sp += 2;
- for (ep = sp; *ep && *ep != '\n'; ep++)
- ;
- fprintf(stderr, " %.*s", (int)(ep - sp), sp);
- }
- fprintf(stderr, "\n");
- }
-}
-#endif /* DEBUG_BISECT */
-
-static struct commit_list *best_bisection(struct commit_list *list, int nr)
-{
- struct commit_list *p, *best;
- int best_distance = -1;
-
- best = list;
- for (p = list; p; p = p->next) {
- int distance;
- unsigned flags = p->item->object.flags;
-
- if (flags & TREESAME)
- continue;
- distance = weight(p);
- if (nr - distance < distance)
- distance = nr - distance;
- if (distance > best_distance) {
- best = p;
- best_distance = distance;
- }
- }
-
- return best;
-}
-
-struct commit_dist {
- struct commit *commit;
- int distance;
-};
-
-static int compare_commit_dist(const void *a_, const void *b_)
-{
- struct commit_dist *a, *b;
-
- a = (struct commit_dist *)a_;
- b = (struct commit_dist *)b_;
- if (a->distance != b->distance)
- return b->distance - a->distance; /* desc sort */
- return hashcmp(a->commit->object.sha1, b->commit->object.sha1);
-}
-
-static struct commit_list *best_bisection_sorted(struct commit_list *list, int nr)
-{
- struct commit_list *p;
- struct commit_dist *array = xcalloc(nr, sizeof(*array));
- int cnt, i;
-
- for (p = list, cnt = 0; p; p = p->next) {
- int distance;
- unsigned flags = p->item->object.flags;
-
- if (flags & TREESAME)
- continue;
- distance = weight(p);
- if (nr - distance < distance)
- distance = nr - distance;
- array[cnt].commit = p->item;
- array[cnt].distance = distance;
- cnt++;
- }
- qsort(array, cnt, sizeof(*array), compare_commit_dist);
- for (p = list, i = 0; i < cnt; i++) {
- struct name_decoration *r = xmalloc(sizeof(*r) + 100);
- struct object *obj = &(array[i].commit->object);
-
- sprintf(r->name, "dist=%d", array[i].distance);
- r->next = add_decoration(&name_decoration, obj, r);
- p->item = array[i].commit;
- p = p->next;
- }
- if (p)
- p->next = NULL;
- free(array);
- return list;
-}
-
-/*
- * zero or positive weight is the number of interesting commits it can
- * reach, including itself. Especially, weight = 0 means it does not
- * reach any tree-changing commits (e.g. just above uninteresting one
- * but traversal is with pathspec).
- *
- * weight = -1 means it has one parent and its distance is yet to
- * be computed.
- *
- * weight = -2 means it has more than one parent and its distance is
- * unknown. After running count_distance() first, they will get zero
- * or positive distance.
- */
-static struct commit_list *do_find_bisection(struct commit_list *list,
- int nr, int *weights,
- int find_all)
-{
- int n, counted;
- struct commit_list *p;
-
- counted = 0;
-
- for (n = 0, p = list; p; p = p->next) {
- struct commit *commit = p->item;
- unsigned flags = commit->object.flags;
-
- p->item->util = &weights[n++];
- switch (count_interesting_parents(commit)) {
- case 0:
- if (!(flags & TREESAME)) {
- weight_set(p, 1);
- counted++;
- show_list("bisection 2 count one",
- counted, nr, list);
- }
- /*
- * otherwise, it is known not to reach any
- * tree-changing commit and gets weight 0.
- */
- break;
- case 1:
- weight_set(p, -1);
- break;
- default:
- weight_set(p, -2);
- break;
- }
- }
-
- show_list("bisection 2 initialize", counted, nr, list);
-
- /*
- * If you have only one parent in the resulting set
- * then you can reach one commit more than that parent
- * can reach. So we do not have to run the expensive
- * count_distance() for single strand of pearls.
- *
- * However, if you have more than one parents, you cannot
- * just add their distance and one for yourself, since
- * they usually reach the same ancestor and you would
- * end up counting them twice that way.
- *
- * So we will first count distance of merges the usual
- * way, and then fill the blanks using cheaper algorithm.
- */
- for (p = list; p; p = p->next) {
- if (p->item->object.flags & UNINTERESTING)
- continue;
- if (weight(p) != -2)
- continue;
- weight_set(p, count_distance(p));
- clear_distance(list);
-
- /* Does it happen to be at exactly half-way? */
- if (!find_all && halfway(p, nr))
- return p;
- counted++;
- }
-
- show_list("bisection 2 count_distance", counted, nr, list);
-
- while (counted < nr) {
- for (p = list; p; p = p->next) {
- struct commit_list *q;
- unsigned flags = p->item->object.flags;
-
- if (0 <= weight(p))
- continue;
- for (q = p->item->parents; q; q = q->next) {
- if (q->item->object.flags & UNINTERESTING)
- continue;
- if (0 <= weight(q))
- break;
- }
- if (!q)
- continue;
-
- /*
- * weight for p is unknown but q is known.
- * add one for p itself if p is to be counted,
- * otherwise inherit it from q directly.
- */
- if (!(flags & TREESAME)) {
- weight_set(p, weight(q)+1);
- counted++;
- show_list("bisection 2 count one",
- counted, nr, list);
- }
- else
- weight_set(p, weight(q));
-
- /* Does it happen to be at exactly half-way? */
- if (!find_all && halfway(p, nr))
- return p;
- }
- }
-
- show_list("bisection 2 counted all", counted, nr, list);
-
- if (!find_all)
- return best_bisection(list, nr);
- else
- return best_bisection_sorted(list, nr);
-}
-
-static struct commit_list *find_bisection(struct commit_list *list,
- int *reaches, int *all,
- int find_all)
-{
- int nr, on_list;
- struct commit_list *p, *best, *next, *last;
- int *weights;
-
- show_list("bisection 2 entry", 0, 0, list);
-
- /*
- * Count the number of total and tree-changing items on the
- * list, while reversing the list.
- */
- for (nr = on_list = 0, last = NULL, p = list;
- p;
- p = next) {
- unsigned flags = p->item->object.flags;
-
- next = p->next;
- if (flags & UNINTERESTING)
- continue;
- p->next = last;
- last = p;
- if (!(flags & TREESAME))
- nr++;
- on_list++;
- }
- list = last;
- show_list("bisection 2 sorted", 0, nr, list);
-
- *all = nr;
- weights = xcalloc(on_list, sizeof(*weights));
-
- /* Do the real work of finding bisection commit. */
- best = do_find_bisection(list, nr, weights, find_all);
- if (best) {
- if (!find_all)
- best->next = NULL;
- *reaches = weight(best);
- }
- free(weights);
- return best;
-}
-
static inline int log2i(int n)
{
int log2 = 0;
@@ -613,11 +223,83 @@ static int estimate_bisect_steps(int all)
return (e < 3 * x) ? n : n - 1;
}
+static void show_tried_revs(struct commit_list *tried, int stringed)
+{
+ printf("bisect_tried='");
+ for (;tried; tried = tried->next) {
+ char *format = tried->next ? "%s|" : "%s";
+ printf(format, sha1_to_hex(tried->item->object.sha1));
+ }
+ printf(stringed ? "' &&\n" : "'\n");
+}
+
+int show_bisect_vars(struct rev_list_info *info, int reaches, int all)
+{
+ int cnt, flags = info->bisect_show_flags;
+ char hex[41] = "", *format;
+ struct commit_list *tried;
+ struct rev_info *revs = info->revs;
+
+ if (!revs->commits && !(flags & BISECT_SHOW_TRIED))
+ return 1;
+
+ revs->commits = filter_skipped(revs->commits, &tried, flags & BISECT_SHOW_ALL);
+
+ /*
+ * revs->commits can reach "reaches" commits among
+ * "all" commits. If it is good, then there are
+ * (all-reaches) commits left to be bisected.
+ * On the other hand, if it is bad, then the set
+ * to bisect is "reaches".
+ * A bisect set of size N has (N-1) commits further
+ * to test, as we already know one bad one.
+ */
+ cnt = all - reaches;
+ if (cnt < reaches)
+ cnt = reaches;
+
+ if (revs->commits)
+ strcpy(hex, sha1_to_hex(revs->commits->item->object.sha1));
+
+ if (flags & BISECT_SHOW_ALL) {
+ traverse_commit_list(revs, show_commit, show_object, info);
+ printf("------\n");
+ }
+
+ if (flags & BISECT_SHOW_TRIED)
+ show_tried_revs(tried, flags & BISECT_SHOW_STRINGED);
+ format = (flags & BISECT_SHOW_STRINGED) ?
+ "bisect_rev=%s &&\n"
+ "bisect_nr=%d &&\n"
+ "bisect_good=%d &&\n"
+ "bisect_bad=%d &&\n"
+ "bisect_all=%d &&\n"
+ "bisect_steps=%d\n"
+ :
+ "bisect_rev=%s\n"
+ "bisect_nr=%d\n"
+ "bisect_good=%d\n"
+ "bisect_bad=%d\n"
+ "bisect_all=%d\n"
+ "bisect_steps=%d\n";
+ printf(format,
+ hex,
+ cnt - 1,
+ all - reaches - 1,
+ reaches - 1,
+ all,
+ estimate_bisect_steps(all));
+
+ return 0;
+}
+
int cmd_rev_list(int argc, const char **argv, const char *prefix)
{
- struct commit_list *list;
+ struct rev_info revs;
+ struct rev_list_info info;
int i;
int read_from_stdin = 0;
+ int bisect_list = 0;
int bisect_show_vars = 0;
int bisect_find_all = 0;
int quiet = 0;
@@ -628,6 +310,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
revs.commit_format = CMIT_FMT_UNSPECIFIED;
argc = setup_revisions(argc, argv, &revs, NULL);
+ memset(&info, 0, sizeof(info));
+ info.revs = &revs;
+
quiet = DIFF_OPT_TST(&revs.diffopt, QUIET);
for (i = 1 ; i < argc; i++) {
const char *arg = argv[i];
@@ -637,7 +322,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
continue;
}
if (!strcmp(arg, "--timestamp")) {
- show_timestamp = 1;
+ info.show_timestamp = 1;
continue;
}
if (!strcmp(arg, "--bisect")) {
@@ -647,6 +332,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
if (!strcmp(arg, "--bisect-all")) {
bisect_list = 1;
bisect_find_all = 1;
+ info.bisect_show_flags = BISECT_SHOW_ALL;
revs.show_decorations = 1;
continue;
}
@@ -666,19 +352,17 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
}
if (revs.commit_format != CMIT_FMT_UNSPECIFIED) {
/* The command line has a --pretty */
- hdr_termination = '\n';
+ info.hdr_termination = '\n';
if (revs.commit_format == CMIT_FMT_ONELINE)
- header_prefix = "";
+ info.header_prefix = "";
else
- header_prefix = "commit ";
+ info.header_prefix = "commit ";
}
else if (revs.verbose_header)
/* Only --header was specified */
revs.commit_format = CMIT_FMT_RAW;
- list = revs.commits;
-
- if ((!list &&
+ if ((!revs.commits &&
(!(revs.tag_objects||revs.tree_objects||revs.blob_objects) &&
!revs.pending.nr)) ||
revs.diff)
@@ -699,49 +383,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix)
revs.commits = find_bisection(revs.commits, &reaches, &all,
bisect_find_all);
- if (bisect_show_vars) {
- int cnt;
- char hex[41];
- if (!revs.commits)
- return 1;
- /*
- * revs.commits can reach "reaches" commits among
- * "all" commits. If it is good, then there are
- * (all-reaches) commits left to be bisected.
- * On the other hand, if it is bad, then the set
- * to bisect is "reaches".
- * A bisect set of size N has (N-1) commits further
- * to test, as we already know one bad one.
- */
- cnt = all - reaches;
- if (cnt < reaches)
- cnt = reaches;
- strcpy(hex, sha1_to_hex(revs.commits->item->object.sha1));
-
- if (bisect_find_all) {
- traverse_commit_list(&revs, show_commit, show_object);
- printf("------\n");
- }
- printf("bisect_rev=%s\n"
- "bisect_nr=%d\n"
- "bisect_good=%d\n"
- "bisect_bad=%d\n"
- "bisect_all=%d\n"
- "bisect_steps=%d\n",
- hex,
- cnt - 1,
- all - reaches - 1,
- reaches - 1,
- all,
- estimate_bisect_steps(all));
- return 0;
- }
+ if (bisect_show_vars)
+ return show_bisect_vars(&info, reaches, all);
}
traverse_commit_list(&revs,
- quiet ? finish_commit : show_commit,
- quiet ? finish_object : show_object);
+ quiet ? finish_commit : show_commit,
+ quiet ? finish_object : show_object,
+ &info);
return 0;
}
diff --git a/builtin.h b/builtin.h
index 1495cf6a2..425ff8e89 100644
--- a/builtin.h
+++ b/builtin.h
@@ -25,6 +25,7 @@ extern int cmd_add(int argc, const char **argv, const char *prefix);
extern int cmd_annotate(int argc, const char **argv, const char *prefix);
extern int cmd_apply(int argc, const char **argv, const char *prefix);
extern int cmd_archive(int argc, const char **argv, const char *prefix);
+extern int cmd_bisect__helper(int argc, const char **argv, const char *prefix);
extern int cmd_blame(int argc, const char **argv, const char *prefix);
extern int cmd_branch(int argc, const char **argv, const char *prefix);
extern int cmd_bundle(int argc, const char **argv, const char *prefix);
diff --git a/config.c b/config.c
index b76fe4c6d..2d70398b1 100644
--- a/config.c
+++ b/config.c
@@ -51,7 +51,7 @@ static char *parse_value(void)
for (;;) {
int c = get_next_char();
- if (len >= sizeof(value))
+ if (len >= sizeof(value) - 1)
return NULL;
if (c == '\n') {
if (quote)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 069e19e82..1a90cb87f 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1888,6 +1888,7 @@ _git ()
--bare
--version
--exec-path
+ --html-path
--work-tree=
--help
"
diff --git a/diff.c b/diff.c
index e0fa78c84..3ac71686e 100644
--- a/diff.c
+++ b/diff.c
@@ -62,6 +62,15 @@ static int parse_diff_color_slot(const char *var, int ofs)
die("bad config variable '%s'", var);
}
+static int git_config_rename(const char *var, const char *value)
+{
+ if (!value)
+ return DIFF_DETECT_RENAME;
+ if (!strcasecmp(value, "copies") || !strcasecmp(value, "copy"))
+ return DIFF_DETECT_COPY;
+ return git_config_bool(var,value) ? DIFF_DETECT_RENAME : 0;
+}
+
/*
* These are to give UI layer defaults.
* The core-level commands such as git-diff-files should
@@ -75,13 +84,7 @@ int git_diff_ui_config(const char *var, const char *value, void *cb)
return 0;
}
if (!strcmp(var, "diff.renames")) {
- if (!value)
- diff_detect_rename_default = DIFF_DETECT_RENAME;
- else if (!strcasecmp(value, "copies") ||
- !strcasecmp(value, "copy"))
- diff_detect_rename_default = DIFF_DETECT_COPY;
- else if (git_config_bool(var,value))
- diff_detect_rename_default = DIFF_DETECT_RENAME;
+ diff_detect_rename_default = git_config_rename(var, value);
return 0;
}
if (!strcmp(var, "diff.autorefreshindex")) {
diff --git a/git-bisect.sh b/git-bisect.sh
index df0ae63b4..24712ff30 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -279,87 +279,14 @@ bisect_auto_next() {
bisect_next_check && bisect_next || :
}
-filter_skipped() {
- _eval="$1"
- _skip="$2"
-
- if [ -z "$_skip" ]; then
- eval "$_eval" | {
- while read line
- do
- echo "$line &&"
- done
- echo ':'
- }
- return
- fi
-
- # Let's parse the output of:
- # "git rev-list --bisect-vars --bisect-all ..."
- eval "$_eval" | {
- VARS= FOUND= TRIED=
- while read hash line
- do
- case "$VARS,$FOUND,$TRIED,$hash" in
- 1,*,*,*)
- # "bisect_foo=bar" read from rev-list output.
- echo "$hash &&"
- ;;
- ,*,*,---*)
- # Separator
- ;;
- ,,,bisect_rev*)
- # We had nothing to search.
- echo "bisect_rev= &&"
- VARS=1
- ;;
- ,,*,bisect_rev*)
- # We did not find a good bisect rev.
- # This should happen only if the "bad"
- # commit is also a "skip" commit.
- echo "bisect_rev='$TRIED' &&"
- VARS=1
- ;;
- ,,*,*)
- # We are searching.
- TRIED="${TRIED:+$TRIED|}$hash"
- case "$_skip" in
- *$hash*) ;;
- *)
- echo "bisect_rev=$hash &&"
- echo "bisect_tried='$TRIED' &&"
- FOUND=1
- ;;
- esac
- ;;
- ,1,*,bisect_rev*)
- # We have already found a rev to be tested.
- VARS=1
- ;;
- ,1,*,*)
- ;;
- *)
- # Unexpected input
- echo "die 'filter_skipped error'"
- die "filter_skipped error " \
- "VARS: '$VARS' " \
- "FOUND: '$FOUND' " \
- "TRIED: '$TRIED' " \
- "hash: '$hash' " \
- "line: '$line'"
- ;;
- esac
- done
- echo ':'
- }
-}
-
exit_if_skipped_commits () {
_tried=$1
- if expr "$_tried" : ".*[|].*" > /dev/null ; then
+ _bad=$2
+ if test -n "$_tried" ; then
echo "There are only 'skip'ped commit left to test."
echo "The first bad commit could be any of:"
echo "$_tried" | tr '[|]' '[\012]'
+ test -n "$_bad" && echo "$_bad"
echo "We cannot bisect more!"
exit 2
fi
@@ -490,28 +417,23 @@ bisect_next() {
test "$?" -eq "1" && return
# Get bisection information
- BISECT_OPT=''
- test -n "$skip" && BISECT_OPT='--bisect-all'
- eval="git rev-list --bisect-vars $BISECT_OPT $good $bad --" &&
- eval="$eval $(cat "$GIT_DIR/BISECT_NAMES")" &&
- eval=$(filter_skipped "$eval" "$skip") &&
+ eval=$(eval "git bisect--helper --next-vars") &&
eval "$eval" || exit
if [ -z "$bisect_rev" ]; then
+ # We should exit here only if the "bad"
+ # commit is also a "skip" commit (see above).
+ exit_if_skipped_commits "$bisect_tried"
echo "$bad was both good and bad"
exit 1
fi
if [ "$bisect_rev" = "$bad" ]; then
- exit_if_skipped_commits "$bisect_tried"
+ exit_if_skipped_commits "$bisect_tried" "$bad"
echo "$bisect_rev is first bad commit"
git diff-tree --pretty $bisect_rev
exit 0
fi
- # We should exit here only if the "bad"
- # commit is also a "skip" commit (see above).
- exit_if_skipped_commits "$bisect_rev"
-
bisect_checkout "$bisect_rev" "$bisect_nr revisions left to test after this (roughly $bisect_steps steps)"
}
diff --git a/git-pull.sh b/git-pull.sh
index 8a2676320..8c750270e 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -90,23 +90,31 @@ error_on_no_merge_candidates () {
curr_branch=${curr_branch#refs/heads/}
- echo "You asked me to pull without telling me which branch you"
- echo "want to merge with, and 'branch.${curr_branch}.merge' in"
- echo "your configuration file does not tell me either. Please"
- echo "name which branch you want to merge on the command line and"
- echo "try again (e.g. 'git pull <repository> <refspec>')."
- echo "See git-pull(1) for details on the refspec."
- echo
- echo "If you often merge with the same branch, you may want to"
- echo "configure the following variables in your configuration"
- echo "file:"
- echo
- echo " branch.${curr_branch}.remote = <nickname>"
- echo " branch.${curr_branch}.merge = <remote-ref>"
- echo " remote.<nickname>.url = <url>"
- echo " remote.<nickname>.fetch = <refspec>"
- echo
- echo "See git-config(1) for details."
+ if [ -z "$curr_branch" ]; then
+ echo "You are not currently on a branch, so I cannot use any"
+ echo "'branch.<branchname>.merge' in your configuration file."
+ echo "Please specify which branch you want to merge on the command"
+ echo "line and try again (e.g. 'git pull <repository> <refspec>')."
+ echo "See git-pull(1) for details."
+ else
+ echo "You asked me to pull without telling me which branch you"
+ echo "want to merge with, and 'branch.${curr_branch}.merge' in"
+ echo "your configuration file does not tell me either. Please"
+ echo "specify which branch you want to merge on the command line and"
+ echo "try again (e.g. 'git pull <repository> <refspec>')."
+ echo "See git-pull(1) for details."
+ echo
+ echo "If you often merge with the same branch, you may want to"
+ echo "configure the following variables in your configuration"
+ echo "file:"
+ echo
+ echo " branch.${curr_branch}.remote = <nickname>"
+ echo " branch.${curr_branch}.merge = <remote-ref>"
+ echo " remote.<nickname>.url = <url>"
+ echo " remote.<nickname>.fetch = <refspec>"
+ echo
+ echo "See git-config(1) for details."
+ fi
exit 1
}
diff --git a/git-svn.perl b/git-svn.perl
index d9197989d..c5965c9aa 100755
--- a/git-svn.perl
+++ b/git-svn.perl
@@ -47,7 +47,8 @@ BEGIN {
# import functions from Git into our packages, en masse
no strict 'refs';
foreach (qw/command command_oneline command_noisy command_output_pipe
- command_input_pipe command_close_pipe/) {
+ command_input_pipe command_close_pipe
+ command_bidi_pipe command_close_bidi_pipe/) {
for my $package ( qw(SVN::Git::Editor SVN::Git::Fetcher
Git::SVN::Migration Git::SVN::Log Git::SVN),
__PACKAGE__) {
@@ -63,7 +64,7 @@ $sha1_short = qr/[a-f\d]{4,40}/;
my ($_stdin, $_help, $_edit,
$_message, $_file,
$_template, $_shared,
- $_version, $_fetch_all, $_no_rebase,
+ $_version, $_fetch_all, $_no_rebase, $_fetch_parent,
$_merge, $_strategy, $_dry_run, $_local,
$_prefix, $_no_checkout, $_url, $_verbose,
$_git_format, $_commit_url, $_tag);
@@ -112,6 +113,7 @@ my %cmd = (
fetch => [ \&cmd_fetch, "Download new revisions from SVN",
{ 'revision|r=s' => \$_revision,
'fetch-all|all' => \$_fetch_all,
+ 'parent|p' => \$_fetch_parent,
%fc_opts } ],
clone => [ \&cmd_clone, "Initialize and fetch revisions",
{ 'revision|r=s' => \$_revision,
@@ -326,6 +328,7 @@ sub do_git_init_db {
command_noisy(@init_db);
$_repository = Git->repository(Repository => ".git");
}
+ command_noisy('config', 'core.autocrlf', 'false');
my $set;
my $pfx = "svn-remote.$Git::SVN::default_repo_id";
foreach my $i (keys %icv) {
@@ -334,6 +337,9 @@ sub do_git_init_db {
command_noisy('config', "$pfx.$i", $icv{$i});
$set = $i;
}
+ my $ignore_regex = \$SVN::Git::Fetcher::_ignore_regex;
+ command_noisy('config', "$pfx.ignore-paths", $$ignore_regex)
+ if defined $$ignore_regex;
}
sub init_subdir {
@@ -381,12 +387,21 @@ sub cmd_fetch {
}
my ($remote) = @_;
if (@_ > 1) {
- die "Usage: $0 fetch [--all] [svn-remote]\n";
+ die "Usage: $0 fetch [--all] [--parent] [svn-remote]\n";
}
- $remote ||= $Git::SVN::default_repo_id;
- if ($_fetch_all) {
+ if ($_fetch_parent) {
+ my ($url, $rev, $uuid, $gs) = working_head_info('HEAD');
+ unless ($gs) {
+ die "Unable to determine upstream SVN information from ",
+ "working tree history\n";
+ }
+ # just fetch, don't checkout.
+ $_no_checkout = 'true';
+ $_fetch_all ? $gs->fetch_all : $gs->fetch;
+ } elsif ($_fetch_all) {
cmd_multi_fetch();
} else {
+ $remote ||= $Git::SVN::default_repo_id;
Git::SVN::fetch_all($remote, Git::SVN::read_all_remotes());
}
}
@@ -1254,6 +1269,40 @@ sub cmt_metadata {
command(qw/cat-file commit/, shift)))[-1]);
}
+sub cmt_sha2rev_batch {
+ my %s2r;
+ my ($pid, $in, $out, $ctx) = command_bidi_pipe(qw/cat-file --batch/);
+ my $list = shift;
+
+ foreach my $sha (@{$list}) {
+ my $first = 1;
+ my $size = 0;
+ print $out $sha, "\n";
+
+ while (my $line = <$in>) {
+ if ($first && $line =~ /^[[:xdigit:]]{40}\smissing$/) {
+ last;
+ } elsif ($first &&
+ $line =~ /^[[:xdigit:]]{40}\scommit\s(\d+)$/) {
+ $first = 0;
+ $size = $1;
+ next;
+ } elsif ($line =~ /^(git-svn-id: )/) {
+ my (undef, $rev, undef) =
+ extract_metadata($line);
+ $s2r{$sha} = $rev;
+ }
+
+ $size -= length($line);
+ last if ($size == 0);
+ }
+ }
+
+ command_close_bidi_pipe($pid, $in, $out, $ctx);
+
+ return \%s2r;
+}
+
sub working_head_info {
my ($head, $refs) = @_;
my @args = ('log', '--no-color', '--first-parent', '--pretty=medium');
@@ -3286,6 +3335,8 @@ sub new {
$self->{empty_symlinks} =
_mark_empty_symlinks($git_svn, $switch_path);
}
+ $self->{ignore_regex} = eval { command_oneline('config', '--get',
+ "svn-remote.$git_svn->{repo_id}.ignore-paths") };
$self->{empty} = {};
$self->{dir_prop} = {};
$self->{file_prop} = {};
@@ -3350,8 +3401,10 @@ sub in_dot_git {
# return value: 0 -- don't ignore, 1 -- ignore
sub is_path_ignored {
- my ($path) = @_;
+ my ($self, $path) = @_;
return 1 if in_dot_git($path);
+ return 1 if defined($self->{ignore_regex}) &&
+ $path =~ m!$self->{ignore_regex}!;
return 0 unless defined($_ignore_regex);
return 1 if $path =~ m!$_ignore_regex!o;
return 0;
@@ -3382,7 +3435,7 @@ sub git_path {
sub delete_entry {
my ($self, $path, $rev, $pb) = @_;
- return undef if is_path_ignored($path);
+ return undef if $self->is_path_ignored($path);
my $gpath = $self->git_path($path);
return undef if ($gpath eq '');
@@ -3415,7 +3468,7 @@ sub open_file {
my ($self, $path, $pb, $rev) = @_;
my ($mode, $blob);
- goto out if is_path_ignored($path);
+ goto out if $self->is_path_ignored($path);
my $gpath = $self->git_path($path);
($mode, $blob) = (command('ls-tree', '-z', $self->{c}, "./$gpath")
@@ -3435,7 +3488,7 @@ sub add_file {
my ($self, $path, $pb, $cp_path, $cp_rev) = @_;
my $mode;
- if (!is_path_ignored($path)) {
+ if (!$self->is_path_ignored($path)) {
my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#);
delete $self->{empty}->{$dir};
$mode = '100644';
@@ -3446,7 +3499,7 @@ sub add_file {
sub add_directory {
my ($self, $path, $cp_path, $cp_rev) = @_;
- goto out if is_path_ignored($path);
+ goto out if $self->is_path_ignored($path);
my $gpath = $self->git_path($path);
if ($gpath eq '') {
my ($ls, $ctx) = command_output_pipe(qw/ls-tree
@@ -3470,7 +3523,7 @@ out:
sub change_dir_prop {
my ($self, $db, $prop, $value) = @_;
- return undef if is_path_ignored($db->{path});
+ return undef if $self->is_path_ignored($db->{path});
$self->{dir_prop}->{$db->{path}} ||= {};
$self->{dir_prop}->{$db->{path}}->{$prop} = $value;
undef;
@@ -3478,7 +3531,7 @@ sub change_dir_prop {
sub absent_directory {
my ($self, $path, $pb) = @_;
- return undef if is_path_ignored($path);
+ return undef if $self->is_path_ignored($path);
$self->{absent_dir}->{$pb->{path}} ||= [];
push @{$self->{absent_dir}->{$pb->{path}}}, $path;
undef;
@@ -3486,7 +3539,7 @@ sub absent_directory {
sub absent_file {
my ($self, $path, $pb) = @_;
- return undef if is_path_ignored($path);
+ return undef if $self->is_path_ignored($path);
$self->{absent_file}->{$pb->{path}} ||= [];
push @{$self->{absent_file}->{$pb->{path}}}, $path;
undef;
@@ -3494,7 +3547,7 @@ sub absent_file {
sub change_file_prop {
my ($self, $fb, $prop, $value) = @_;
- return undef if is_path_ignored($fb->{path});
+ return undef if $self->is_path_ignored($fb->{path});
if ($prop eq 'svn:executable') {
if ($fb->{mode_b} != 120000) {
$fb->{mode_b} = defined $value ? 100755 : 100644;
@@ -3510,7 +3563,7 @@ sub change_file_prop {
sub apply_textdelta {
my ($self, $fb, $exp) = @_;
- return undef if is_path_ignored($fb->{path});
+ return undef if $self->is_path_ignored($fb->{path});
my $fh = $::_repository->temp_acquire('svn_delta');
# $fh gets auto-closed() by SVN::TxDelta::apply(),
# (but $base does not,) so dup() it for reading in close_file
@@ -3557,7 +3610,7 @@ sub apply_textdelta {
sub close_file {
my ($self, $fb, $exp) = @_;
- return undef if is_path_ignored($fb->{path});
+ return undef if $self->is_path_ignored($fb->{path});
my $hash;
my $path = $self->git_path($fb->{path});
@@ -4991,11 +5044,22 @@ sub cmd_blame {
'--', $path);
my ($sha1);
my %authors;
+ my @buffer;
+ my %dsha; #distinct sha keys
+
while (my $line = <$fh>) {
+ push @buffer, $line;
+ if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
+ $dsha{$1} = 1;
+ }
+ }
+
+ my $s2r = ::cmt_sha2rev_batch([keys %dsha]);
+
+ foreach my $line (@buffer) {
if ($line =~ /^([[:xdigit:]]{40})\s\d+\s\d+/) {
- $sha1 = $1;
- (undef, $rev, undef) = ::cmt_metadata($1);
- $rev = '0' if (!$rev);
+ $rev = $s2r->{$1};
+ $rev = '0' if (!$rev)
}
elsif ($line =~ /^author (.*)/) {
$authors{$rev} = $1;
diff --git a/git.c b/git.c
index c2b181ed7..bfb6508ad 100644
--- a/git.c
+++ b/git.c
@@ -5,7 +5,7 @@
#include "run-command.h"
const char git_usage_string[] =
- "git [--version] [--exec-path[=GIT_EXEC_PATH]] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
+ "git [--version] [--exec-path[=GIT_EXEC_PATH]] [--html-path] [-p|--paginate|--no-pager] [--bare] [--git-dir=GIT_DIR] [--work-tree=GIT_WORK_TREE] [--help] COMMAND [ARGS]";
const char git_more_info_string[] =
"See 'git help COMMAND' for more information on a specific command.";
@@ -75,6 +75,9 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
puts(git_exec_path());
exit(0);
}
+ } else if (!strcmp(cmd, "--html-path")) {
+ puts(system_path(GIT_HTML_PATH));
+ exit(0);
} else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
use_pager = 1;
} else if (!strcmp(cmd, "--no-pager")) {
@@ -271,6 +274,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "annotate", cmd_annotate, RUN_SETUP },
{ "apply", cmd_apply },
{ "archive", cmd_archive },
+ { "bisect--helper", cmd_bisect__helper, RUN_SETUP | NEED_WORK_TREE },
{ "blame", cmd_blame, RUN_SETUP },
{ "branch", cmd_branch, RUN_SETUP },
{ "bundle", cmd_bundle },
diff --git a/http-push.c b/http-push.c
index e465b20c1..5138224cc 100644
--- a/http-push.c
+++ b/http-push.c
@@ -186,6 +186,32 @@ enum dav_header_flag {
DAV_HEADER_TIMEOUT = (1u << 2)
};
+static char *xml_entities(char *s)
+{
+ struct strbuf buf = STRBUF_INIT;
+ while (*s) {
+ size_t len = strcspn(s, "\"<>&");
+ strbuf_add(&buf, s, len);
+ s += len;
+ switch (*s) {
+ case '"':
+ strbuf_addstr(&buf, "&quot;");
+ break;
+ case '<':
+ strbuf_addstr(&buf, "&lt;");
+ break;
+ case '>':
+ strbuf_addstr(&buf, "&gt;");
+ break;
+ case '&':
+ strbuf_addstr(&buf, "&amp;");
+ break;
+ }
+ s++;
+ }
+ return strbuf_detach(&buf, NULL);
+}
+
static struct curl_slist *get_dav_token_headers(struct remote_lock *lock, enum dav_header_flag options)
{
struct strbuf buf = STRBUF_INIT;
@@ -567,6 +593,10 @@ static void start_put(struct transfer_request *request)
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &request->buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, request->buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &request->buffer);
+#endif
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1);
@@ -1221,6 +1251,7 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
struct remote_lock *lock = NULL;
struct curl_slist *dav_headers = NULL;
struct xml_ctx ctx;
+ char *escaped;
url = xmalloc(strlen(repo->url) + strlen(path) + 1);
sprintf(url, "%s%s", repo->url, path);
@@ -1255,7 +1286,9 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
ep = strchr(ep + 1, '/');
}
- strbuf_addf(&out_buffer.buf, LOCK_REQUEST, git_default_email);
+ escaped = xml_entities(git_default_email);
+ strbuf_addf(&out_buffer.buf, LOCK_REQUEST, escaped);
+ free(escaped);
sprintf(timeout_header, "Timeout: Second-%ld", timeout);
dav_headers = curl_slist_append(dav_headers, timeout_header);
@@ -1266,6 +1299,10 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -1507,6 +1544,10 @@ static void remote_ls(const char *path, int flags,
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_URL, url);
@@ -1572,8 +1613,11 @@ static int locking_available(void)
struct curl_slist *dav_headers = NULL;
struct xml_ctx ctx;
int lock_flags = 0;
+ char *escaped;
- strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, repo->url);
+ escaped = xml_entities(repo->url);
+ strbuf_addf(&out_buffer.buf, PROPFIND_SUPPORTEDLOCK_REQUEST, escaped);
+ free(escaped);
dav_headers = curl_slist_append(dav_headers, "Depth: 0");
dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml");
@@ -1583,6 +1627,10 @@ static int locking_available(void)
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer);
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer);
curl_easy_setopt(slot->curl, CURLOPT_URL, repo->url);
@@ -1765,6 +1813,10 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock)
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &out_buffer);
+#endif
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
@@ -1909,6 +1961,10 @@ static void update_remote_info_refs(struct remote_lock *lock)
curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer);
curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.buf.len);
curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer);
+#ifndef NO_CURL_IOCTL
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLFUNCTION, ioctl_buffer);
+ curl_easy_setopt(slot->curl, CURLOPT_IOCTLDATA, &buffer);
+#endif
curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null);
curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT);
curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers);
diff --git a/http.c b/http.c
index 2fc55d671..2e3d6493e 100644
--- a/http.c
+++ b/http.c
@@ -44,6 +44,25 @@ size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
return size;
}
+#ifndef NO_CURL_IOCTL
+curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp)
+{
+ struct buffer *buffer = clientp;
+
+ switch (cmd) {
+ case CURLIOCMD_NOP:
+ return CURLIOE_OK;
+
+ case CURLIOCMD_RESTARTREAD:
+ buffer->posn = 0;
+ return CURLIOE_OK;
+
+ default:
+ return CURLIOE_UNKNOWNCMD;
+ }
+}
+#endif
+
size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *buffer_)
{
size_t size = eltsize * nmemb;
diff --git a/http.h b/http.h
index 905b4629a..26abebed1 100644
--- a/http.h
+++ b/http.h
@@ -37,6 +37,10 @@
#define CURLE_HTTP_RETURNED_ERROR CURLE_HTTP_NOT_FOUND
#endif
+#if LIBCURL_VERSION_NUM < 0x070c03
+#define NO_CURL_IOCTL
+#endif
+
struct slot_results
{
CURLcode curl_result;
@@ -67,6 +71,9 @@ struct buffer
extern size_t fread_buffer(void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
extern size_t fwrite_buffer(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
extern size_t fwrite_null(const void *ptr, size_t eltsize, size_t nmemb, void *strbuf);
+#ifndef NO_CURL_IOCTL
+extern curlioerr ioctl_buffer(CURL *handle, int cmd, void *clientp);
+#endif
/* Slot lifecycle functions */
extern struct active_request_slot *get_active_slot(void);
diff --git a/list-objects.c b/list-objects.c
index c8b8375e4..433394a10 100644
--- a/list-objects.c
+++ b/list-objects.c
@@ -23,7 +23,6 @@ static void process_blob(struct rev_info *revs,
if (obj->flags & (UNINTERESTING | SEEN))
return;
obj->flags |= SEEN;
- name = xstrdup(name);
add_object(obj, p, path, name);
}
@@ -78,7 +77,6 @@ static void process_tree(struct rev_info *revs,
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
obj->flags |= SEEN;
- name = xstrdup(name);
add_object(obj, p, path, name);
me.up = path;
me.elem = name;
@@ -137,8 +135,9 @@ void mark_edges_uninteresting(struct commit_list *list,
}
void traverse_commit_list(struct rev_info *revs,
- void (*show_commit)(struct commit *),
- void (*show_object)(struct object_array_entry *))
+ show_commit_fn show_commit,
+ show_object_fn show_object,
+ void *data)
{
int i;
struct commit *commit;
@@ -146,7 +145,7 @@ void traverse_commit_list(struct rev_info *revs,
while ((commit = get_revision(revs)) != NULL) {
process_tree(revs, commit->tree, &objects, NULL, "");
- show_commit(commit);
+ show_commit(commit, data);
}
for (i = 0; i < revs->pending.nr; i++) {
struct object_array_entry *pending = revs->pending.objects + i;
@@ -173,7 +172,7 @@ void traverse_commit_list(struct rev_info *revs,
sha1_to_hex(obj->sha1), name);
}
for (i = 0; i < objects.nr; i++)
- show_object(&objects.objects[i]);
+ show_object(&objects.objects[i], data);
free(objects.objects);
if (revs->pending.nr) {
free(revs->pending.objects);
diff --git a/list-objects.h b/list-objects.h
index 0f41391ec..47fae2e46 100644
--- a/list-objects.h
+++ b/list-objects.h
@@ -1,11 +1,11 @@
#ifndef LIST_OBJECTS_H
#define LIST_OBJECTS_H
-typedef void (*show_commit_fn)(struct commit *);
-typedef void (*show_object_fn)(struct object_array_entry *);
+typedef void (*show_commit_fn)(struct commit *, void *);
+typedef void (*show_object_fn)(struct object_array_entry *, void *);
typedef void (*show_edge_fn)(struct commit *);
-void traverse_commit_list(struct rev_info *revs, show_commit_fn, show_object_fn);
+void traverse_commit_list(struct rev_info *, show_commit_fn, show_object_fn, void *);
void mark_edges_uninteresting(struct commit_list *, struct rev_info *, show_edge_fn);
diff --git a/merge-recursive.c b/merge-recursive.c
index 3e1bc3e07..d6f058223 100644
--- a/merge-recursive.c
+++ b/merge-recursive.c
@@ -1116,21 +1116,13 @@ static int process_entry(struct merge_options *o,
o->branch1, o->branch2);
clean_merge = mfi.clean;
- if (mfi.clean)
- update_file(o, 1, mfi.sha, mfi.mode, path);
- else if (S_ISGITLINK(mfi.mode))
- output(o, 1, "CONFLICT (submodule): Merge conflict in %s "
- "- needs %s", path, sha1_to_hex(b.sha1));
- else {
+ if (!mfi.clean) {
+ if (S_ISGITLINK(mfi.mode))
+ reason = "submodule";
output(o, 1, "CONFLICT (%s): Merge conflict in %s",
reason, path);
-
- if (o->call_depth)
- update_file(o, 0, mfi.sha, mfi.mode, path);
- else
- update_file_flags(o, mfi.sha, mfi.mode, path,
- 0 /* update_cache */, 1 /* update_working_directory */);
}
+ update_file(o, mfi.clean, mfi.sha, mfi.mode, path);
} else if (!o_sha && !a_sha && !b_sha) {
/*
* this entry was deleted altogether. a_mode == 0 means
diff --git a/patch-ids.c b/patch-ids.c
index 3be5d3165..571725705 100644
--- a/patch-ids.c
+++ b/patch-ids.c
@@ -1,6 +1,7 @@
#include "cache.h"
#include "diff.h"
#include "commit.h"
+#include "sha1-lookup.h"
#include "patch-ids.h"
static int commit_patch_id(struct commit *commit, struct diff_options *options,
@@ -15,99 +16,15 @@ static int commit_patch_id(struct commit *commit, struct diff_options *options,
return diff_flush_patch_id(options, sha1);
}
-static uint32_t take2(const unsigned char *id)
+static const unsigned char *patch_id_access(size_t index, void *table)
{
- return ((id[0] << 8) | id[1]);
+ struct patch_id **id_table = table;
+ return id_table[index]->patch_id;
}
-/*
- * Conventional binary search loop looks like this:
- *
- * do {
- * int mi = (lo + hi) / 2;
- * int cmp = "entry pointed at by mi" minus "target";
- * if (!cmp)
- * return (mi is the wanted one)
- * if (cmp > 0)
- * hi = mi; "mi is larger than target"
- * else
- * lo = mi+1; "mi is smaller than target"
- * } while (lo < hi);
- *
- * The invariants are:
- *
- * - When entering the loop, lo points at a slot that is never
- * above the target (it could be at the target), hi points at a
- * slot that is guaranteed to be above the target (it can never
- * be at the target).
- *
- * - We find a point 'mi' between lo and hi (mi could be the same
- * as lo, but never can be the same as hi), and check if it hits
- * the target. There are three cases:
- *
- * - if it is a hit, we are happy.
- *
- * - if it is strictly higher than the target, we update hi with
- * it.
- *
- * - if it is strictly lower than the target, we update lo to be
- * one slot after it, because we allow lo to be at the target.
- *
- * When choosing 'mi', we do not have to take the "middle" but
- * anywhere in between lo and hi, as long as lo <= mi < hi is
- * satisfied. When we somehow know that the distance between the
- * target and lo is much shorter than the target and hi, we could
- * pick mi that is much closer to lo than the midway.
- */
static int patch_pos(struct patch_id **table, int nr, const unsigned char *id)
{
- int hi = nr;
- int lo = 0;
- int mi = 0;
-
- if (!nr)
- return -1;
-
- if (nr != 1) {
- unsigned lov, hiv, miv, ofs;
-
- for (ofs = 0; ofs < 18; ofs += 2) {
- lov = take2(table[0]->patch_id + ofs);
- hiv = take2(table[nr-1]->patch_id + ofs);
- miv = take2(id + ofs);
- if (miv < lov)
- return -1;
- if (hiv < miv)
- return -1 - nr;
- if (lov != hiv) {
- /*
- * At this point miv could be equal
- * to hiv (but id could still be higher);
- * the invariant of (mi < hi) should be
- * kept.
- */
- mi = (nr-1) * (miv - lov) / (hiv - lov);
- if (lo <= mi && mi < hi)
- break;
- die("oops");
- }
- }
- if (18 <= ofs)
- die("cannot happen -- lo and hi are identical");
- }
-
- do {
- int cmp;
- cmp = hashcmp(table[mi]->patch_id, id);
- if (!cmp)
- return mi;
- if (cmp > 0)
- hi = mi;
- else
- lo = mi + 1;
- mi = (hi + lo) / 2;
- } while (lo < hi);
- return -lo-1;
+ return sha1_pos(id, table, nr, patch_id_access);
}
#define BUCKET_SIZE 190 /* 190 * 21 = 3990, with slop close enough to 4K */
diff --git a/perl/Git.pm b/perl/Git.pm
index 7d7f2b1d3..291ff5b53 100644
--- a/perl/Git.pm
+++ b/perl/Git.pm
@@ -56,7 +56,7 @@ require Exporter;
@EXPORT_OK = qw(command command_oneline command_noisy
command_output_pipe command_input_pipe command_close_pipe
command_bidi_pipe command_close_bidi_pipe
- version exec_path hash_object git_cmd_try
+ version exec_path html_path hash_object git_cmd_try
remote_refs
temp_acquire temp_release temp_reset temp_path);
@@ -492,6 +492,16 @@ C<git --exec-path>). Useful mostly only internally.
sub exec_path { command_oneline('--exec-path') }
+=item html_path ()
+
+Return path to the Git html documentation (the same as
+C<git --html-path>). Useful mostly only internally.
+
+=cut
+
+sub html_path { command_oneline('--html-path') }
+
+
=item repo_path ()
Return path to the git repository. Must be called on a repository instance.
diff --git a/quote.c b/quote.c
index 6a520855d..7a49fcf69 100644
--- a/quote.c
+++ b/quote.c
@@ -72,7 +72,7 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
}
}
-char *sq_dequote(char *arg)
+char *sq_dequote_step(char *arg, char **next)
{
char *dst = arg;
char *src = arg;
@@ -92,6 +92,8 @@ char *sq_dequote(char *arg)
switch (*++src) {
case '\0':
*dst = 0;
+ if (next)
+ *next = NULL;
return arg;
case '\\':
c = *++src;
@@ -101,11 +103,40 @@ char *sq_dequote(char *arg)
}
/* Fallthrough */
default:
- return NULL;
+ if (!next || !isspace(*src))
+ return NULL;
+ do {
+ c = *++src;
+ } while (isspace(c));
+ *dst = 0;
+ *next = src;
+ return arg;
}
}
}
+char *sq_dequote(char *arg)
+{
+ return sq_dequote_step(arg, NULL);
+}
+
+int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc)
+{
+ char *next = arg;
+
+ if (!*arg)
+ return 0;
+ do {
+ char *dequoted = sq_dequote_step(next, &next);
+ if (!dequoted)
+ return -1;
+ ALLOC_GROW(*argv, *nr + 1, *alloc);
+ (*argv)[(*nr)++] = dequoted;
+ } while (next);
+
+ return 0;
+}
+
/* 1 means: quote as octal
* 0 means: quote as octal if (quote_path_fully)
* -1 means: never quote
diff --git a/quote.h b/quote.h
index c5eea6f18..66730f2bf 100644
--- a/quote.h
+++ b/quote.h
@@ -39,6 +39,15 @@ extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen);
*/
extern char *sq_dequote(char *);
+/*
+ * Same as the above, but can be used to unwrap many arguments in the
+ * same string separated by space. "next" is changed to point to the
+ * next argument that should be passed as first parameter. When there
+ * is no more argument to be dequoted, "next" is updated to point to NULL.
+ */
+extern char *sq_dequote_step(char *arg, char **next);
+extern int sq_dequote_to_argv(char *arg, const char ***argv, int *nr, int *alloc);
+
extern int unquote_c_style(struct strbuf *, const char *quoted, const char **endp);
extern size_t quote_c_style(const char *name, struct strbuf *, FILE *, int no_dq);
extern void quote_two_c_style(struct strbuf *, const char *, const char *, int);
diff --git a/reachable.c b/reachable.c
index 3b1c18ff9..b515fa2de 100644
--- a/reachable.c
+++ b/reachable.c
@@ -48,7 +48,6 @@ static void process_tree(struct tree *tree,
obj->flags |= SEEN;
if (parse_tree(tree) < 0)
die("bad tree object %s", sha1_to_hex(obj->sha1));
- name = xstrdup(name);
add_object(obj, p, path, name);
me.up = path;
me.elem = name;
diff --git a/refs.c b/refs.c
index 59c373fc6..82afb8768 100644
--- a/refs.c
+++ b/refs.c
@@ -647,19 +647,24 @@ int for_each_ref(each_ref_fn fn, void *cb_data)
return do_for_each_ref("refs/", fn, 0, 0, cb_data);
}
+int for_each_ref_in(const char *prefix, each_ref_fn fn, void *cb_data)
+{
+ return do_for_each_ref(prefix, fn, strlen(prefix), 0, cb_data);
+}
+
int for_each_tag_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/tags/", fn, 10, 0, cb_data);
+ return for_each_ref_in("refs/tags/", fn, cb_data);
}
int for_each_branch_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/heads/", fn, 11, 0, cb_data);
+ return for_each_ref_in("refs/heads/", fn, cb_data);
}
int for_each_remote_ref(each_ref_fn fn, void *cb_data)
{
- return do_for_each_ref("refs/remotes/", fn, 13, 0, cb_data);
+ return for_each_ref_in("refs/remotes/", fn, cb_data);
}
int for_each_rawref(each_ref_fn fn, void *cb_data)
@@ -1652,3 +1657,102 @@ struct ref *find_ref_by_name(const struct ref *list, const char *name)
return (struct ref *)list;
return NULL;
}
+
+/*
+ * generate a format suitable for scanf from a ref_rev_parse_rules
+ * rule, that is replace the "%.*s" spec with a "%s" spec
+ */
+static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
+{
+ char *spec;
+
+ spec = strstr(rule, "%.*s");
+ if (!spec || strstr(spec + 4, "%.*s"))
+ die("invalid rule in ref_rev_parse_rules: %s", rule);
+
+ /* copy all until spec */
+ strncpy(scanf_fmt, rule, spec - rule);
+ scanf_fmt[spec - rule] = '\0';
+ /* copy new spec */
+ strcat(scanf_fmt, "%s");
+ /* copy remaining rule */
+ strcat(scanf_fmt, spec + 4);
+
+ return;
+}
+
+char *shorten_unambiguous_ref(const char *ref)
+{
+ int i;
+ static char **scanf_fmts;
+ static int nr_rules;
+ char *short_name;
+
+ /* pre generate scanf formats from ref_rev_parse_rules[] */
+ if (!nr_rules) {
+ size_t total_len = 0;
+
+ /* the rule list is NULL terminated, count them first */
+ for (; ref_rev_parse_rules[nr_rules]; nr_rules++)
+ /* no +1 because strlen("%s") < strlen("%.*s") */
+ total_len += strlen(ref_rev_parse_rules[nr_rules]);
+
+ scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
+
+ total_len = 0;
+ for (i = 0; i < nr_rules; i++) {
+ scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
+ + total_len;
+ gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
+ total_len += strlen(ref_rev_parse_rules[i]);
+ }
+ }
+
+ /* bail out if there are no rules */
+ if (!nr_rules)
+ return xstrdup(ref);
+
+ /* buffer for scanf result, at most ref must fit */
+ short_name = xstrdup(ref);
+
+ /* skip first rule, it will always match */
+ for (i = nr_rules - 1; i > 0 ; --i) {
+ int j;
+ int short_name_len;
+
+ if (1 != sscanf(ref, scanf_fmts[i], short_name))
+ continue;
+
+ short_name_len = strlen(short_name);
+
+ /*
+ * check if the short name resolves to a valid ref,
+ * but use only rules prior to the matched one
+ */
+ for (j = 0; j < i; j++) {
+ const char *rule = ref_rev_parse_rules[j];
+ unsigned char short_objectname[20];
+ char refname[PATH_MAX];
+
+ /*
+ * the short name is ambiguous, if it resolves
+ * (with this previous rule) to a valid ref
+ * read_ref() returns 0 on success
+ */
+ mksnpath(refname, sizeof(refname),
+ rule, short_name_len, short_name);
+ if (!read_ref(refname, short_objectname))
+ break;
+ }
+
+ /*
+ * short name is non-ambiguous if all previous rules
+ * haven't resolved to a valid ref
+ */
+ if (j == i)
+ return short_name;
+ }
+
+ free(short_name);
+ return xstrdup(ref);
+}
diff --git a/refs.h b/refs.h
index 68c2d16d5..50abbbb2a 100644
--- a/refs.h
+++ b/refs.h
@@ -20,6 +20,7 @@ struct ref_lock {
typedef int each_ref_fn(const char *refname, const unsigned char *sha1, int flags, void *cb_data);
extern int head_ref(each_ref_fn, void *);
extern int for_each_ref(each_ref_fn, void *);
+extern int for_each_ref_in(const char *, each_ref_fn, void *);
extern int for_each_tag_ref(each_ref_fn, void *);
extern int for_each_branch_ref(each_ref_fn, void *);
extern int for_each_remote_ref(each_ref_fn, void *);
@@ -80,6 +81,7 @@ extern int for_each_reflog(each_ref_fn, void *);
extern int check_ref_format(const char *target);
extern const char *prettify_ref(const struct ref *ref);
+extern char *shorten_unambiguous_ref(const char *ref);
/** rename ref, return 0 on success **/
extern int rename_ref(const char *oldref, const char *newref, const char *logmsg);
diff --git a/remote.c b/remote.c
index d12140e0f..e4c89b8b6 100644
--- a/remote.c
+++ b/remote.c
@@ -667,6 +667,17 @@ struct remote *remote_get(const char *name)
return ret;
}
+int remote_is_configured(const char *name)
+{
+ int i;
+ read_config();
+
+ for (i = 0; i < remotes_nr; i++)
+ if (!strcmp(name, remotes[i]->name))
+ return 1;
+ return 0;
+}
+
int for_each_remote(each_remote_fn fn, void *priv)
{
int i, result = 0;
@@ -1493,7 +1504,7 @@ static int one_local_ref(const char *refname, const unsigned char *sha1, int fla
struct ref *get_local_heads(void)
{
- struct ref *local_refs, **local_tail = &local_refs;
+ struct ref *local_refs = NULL, **local_tail = &local_refs;
for_each_ref(one_local_ref, &local_tail);
return local_refs;
}
diff --git a/remote.h b/remote.h
index de3d21b66..99706a89b 100644
--- a/remote.h
+++ b/remote.h
@@ -45,6 +45,7 @@ struct remote {
};
struct remote *remote_get(const char *name);
+int remote_is_configured(const char *name);
typedef int each_remote_fn(struct remote *remote, void *priv);
int for_each_remote(each_remote_fn fn, void *priv);
diff --git a/sha1-lookup.c b/sha1-lookup.c
index da357479c..c4dc55d1f 100644
--- a/sha1-lookup.c
+++ b/sha1-lookup.c
@@ -1,6 +1,107 @@
#include "cache.h"
#include "sha1-lookup.h"
+static uint32_t take2(const unsigned char *sha1)
+{
+ return ((sha1[0] << 8) | sha1[1]);
+}
+
+/*
+ * Conventional binary search loop looks like this:
+ *
+ * do {
+ * int mi = (lo + hi) / 2;
+ * int cmp = "entry pointed at by mi" minus "target";
+ * if (!cmp)
+ * return (mi is the wanted one)
+ * if (cmp > 0)
+ * hi = mi; "mi is larger than target"
+ * else
+ * lo = mi+1; "mi is smaller than target"
+ * } while (lo < hi);
+ *
+ * The invariants are:
+ *
+ * - When entering the loop, lo points at a slot that is never
+ * above the target (it could be at the target), hi points at a
+ * slot that is guaranteed to be above the target (it can never
+ * be at the target).
+ *
+ * - We find a point 'mi' between lo and hi (mi could be the same
+ * as lo, but never can be the same as hi), and check if it hits
+ * the target. There are three cases:
+ *
+ * - if it is a hit, we are happy.
+ *
+ * - if it is strictly higher than the target, we update hi with
+ * it.
+ *
+ * - if it is strictly lower than the target, we update lo to be
+ * one slot after it, because we allow lo to be at the target.
+ *
+ * When choosing 'mi', we do not have to take the "middle" but
+ * anywhere in between lo and hi, as long as lo <= mi < hi is
+ * satisfied. When we somehow know that the distance between the
+ * target and lo is much shorter than the target and hi, we could
+ * pick mi that is much closer to lo than the midway.
+ */
+/*
+ * The table should contain "nr" elements.
+ * The sha1 of element i (between 0 and nr - 1) should be returned
+ * by "fn(i, table)".
+ */
+int sha1_pos(const unsigned char *sha1, void *table, size_t nr,
+ sha1_access_fn fn)
+{
+ size_t hi = nr;
+ size_t lo = 0;
+ size_t mi = 0;
+
+ if (!nr)
+ return -1;
+
+ if (nr != 1) {
+ size_t lov, hiv, miv, ofs;
+
+ for (ofs = 0; ofs < 18; ofs += 2) {
+ lov = take2(fn(0, table) + ofs);
+ hiv = take2(fn(nr - 1, table) + ofs);
+ miv = take2(sha1 + ofs);
+ if (miv < lov)
+ return -1;
+ if (hiv < miv)
+ return -1 - nr;
+ if (lov != hiv) {
+ /*
+ * At this point miv could be equal
+ * to hiv (but sha1 could still be higher);
+ * the invariant of (mi < hi) should be
+ * kept.
+ */
+ mi = (nr - 1) * (miv - lov) / (hiv - lov);
+ if (lo <= mi && mi < hi)
+ break;
+ die("BUG: assertion failed in binary search");
+ }
+ }
+ if (18 <= ofs)
+ die("cannot happen -- lo and hi are identical");
+ }
+
+ do {
+ int cmp;
+ cmp = hashcmp(fn(mi, table), sha1);
+ if (!cmp)
+ return mi;
+ if (cmp > 0)
+ hi = mi;
+ else
+ lo = mi + 1;
+ mi = (hi + lo) / 2;
+ } while (lo < hi);
+ return -lo-1;
+}
+
/*
* Conventional binary search loop looks like this:
*
diff --git a/sha1-lookup.h b/sha1-lookup.h
index 3249a81b3..20af28568 100644
--- a/sha1-lookup.h
+++ b/sha1-lookup.h
@@ -1,6 +1,13 @@
#ifndef SHA1_LOOKUP_H
#define SHA1_LOOKUP_H
+typedef const unsigned char *sha1_access_fn(size_t index, void *table);
+
+extern int sha1_pos(const unsigned char *sha1,
+ void *table,
+ size_t nr,
+ sha1_access_fn fn);
+
extern int sha1_entry_pos(const void *table,
size_t elem_size,
size_t key_offset,
diff --git a/t/lib-git-svn.sh b/t/lib-git-svn.sh
index cdd7ccdd2..773d47cf3 100644
--- a/t/lib-git-svn.sh
+++ b/t/lib-git-svn.sh
@@ -8,6 +8,10 @@ then
say 'skipping git svn tests, NO_SVN_TESTS defined'
test_done
fi
+if ! test_have_prereq PERL; then
+ say 'skipping git svn tests, perl not available'
+ test_done
+fi
GIT_DIR=$PWD/.git
GIT_SVN_DIR=$GIT_DIR/svn/git-svn
diff --git a/t/t1303-wacky-config.sh b/t/t1303-wacky-config.sh
index 1983076c7..080117c6b 100755
--- a/t/t1303-wacky-config.sh
+++ b/t/t1303-wacky-config.sh
@@ -10,7 +10,7 @@ setup() {
check() {
echo "$2" >expected
- git config --get "$1" >actual
+ git config --get "$1" >actual 2>&1
test_cmp actual expected
}
@@ -40,4 +40,11 @@ test_expect_success 'make sure git config escapes section names properly' '
check "$SECTION" bar
'
+LONG_VALUE=$(printf "x%01021dx a" 7)
+test_expect_success 'do not crash on special long config line' '
+ setup &&
+ git config section.key "$LONG_VALUE" &&
+ check section.key "fatal: bad config file line 2 in .git/config"
+'
+
test_done
diff --git a/t/t2013-checkout-submodule.sh b/t/t2013-checkout-submodule.sh
new file mode 100755
index 000000000..fda3f0af7
--- /dev/null
+++ b/t/t2013-checkout-submodule.sh
@@ -0,0 +1,42 @@
+#!/bin/sh
+
+test_description='checkout can handle submodules'
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ mkdir submodule &&
+ (cd submodule &&
+ git init &&
+ test_commit first) &&
+ git add submodule &&
+ test_tick &&
+ git commit -m superproject &&
+ (cd submodule &&
+ test_commit second) &&
+ git add submodule &&
+ test_tick &&
+ git commit -m updated.superproject
+'
+
+test_expect_success '"reset <submodule>" updates the index' '
+ git update-index --refresh &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD &&
+ test_must_fail git reset HEAD^ submodule &&
+ test_must_fail git diff-files --quiet &&
+ git reset submodule &&
+ git diff-files --quiet
+'
+
+test_expect_success '"checkout <submodule>" updates the index only' '
+ git update-index --refresh &&
+ git diff-files --quiet &&
+ git diff-index --quiet --cached HEAD &&
+ git checkout HEAD^ submodule &&
+ test_must_fail git diff-files --quiet &&
+ git checkout HEAD submodule &&
+ git diff-files --quiet
+'
+
+test_done
diff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh
index 4dd7d12ba..51cb4a30f 100755
--- a/t/t3101-ls-tree-dirname.sh
+++ b/t/t3101-ls-tree-dirname.sh
@@ -135,4 +135,10 @@ test_expect_success \
EOF
test_output'
+test_expect_success 'ls-tree filter is leading path match' '
+ git ls-tree $tree pa path3/a >current &&
+ >expected &&
+ test_output
+'
+
test_done
diff --git a/t/t3701-add-interactive.sh b/t/t3701-add-interactive.sh
index fe017839c..dfc65601a 100755
--- a/t/t3701-add-interactive.sh
+++ b/t/t3701-add-interactive.sh
@@ -3,6 +3,11 @@
test_description='add -i basic tests'
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping git add -i tests, perl not available'
+ test_done
+fi
+
test_expect_success 'setup (initial)' '
echo content >file &&
git add file &&
diff --git a/t/t4010-diff-pathspec.sh b/t/t4010-diff-pathspec.sh
index 9322298ec..94df7ae53 100755
--- a/t/t4010-diff-pathspec.sh
+++ b/t/t4010-diff-pathspec.sh
@@ -62,4 +62,12 @@ test_expect_success \
'git diff-index --cached $tree -- file0/ >current &&
compare_diff_raw current expected'
+test_expect_success 'diff-tree pathspec' '
+ tree2=$(git write-tree) &&
+ echo "$tree2" &&
+ git diff-tree -r --name-only $tree $tree2 -- pa path1/a >current &&
+ >expected &&
+ test_cmp expected current
+'
+
test_done
diff --git a/t/t5506-remote-groups.sh b/t/t5506-remote-groups.sh
new file mode 100755
index 000000000..2a1806b0b
--- /dev/null
+++ b/t/t5506-remote-groups.sh
@@ -0,0 +1,81 @@
+#!/bin/sh
+
+test_description='git remote group handling'
+. ./test-lib.sh
+
+mark() {
+ echo "$1" >mark
+}
+
+update_repo() {
+ (cd $1 &&
+ echo content >>file &&
+ git add file &&
+ git commit -F ../mark)
+}
+
+update_repos() {
+ update_repo one $1 &&
+ update_repo two $1
+}
+
+repo_fetched() {
+ if test "`git log -1 --pretty=format:%s $1 --`" = "`cat mark`"; then
+ echo >&2 "repo was fetched: $1"
+ return 0
+ fi
+ echo >&2 "repo was not fetched: $1"
+ return 1
+}
+
+test_expect_success 'setup' '
+ mkdir one && (cd one && git init) &&
+ mkdir two && (cd two && git init) &&
+ git remote add -m master one one &&
+ git remote add -m master two two
+'
+
+test_expect_success 'no group updates all' '
+ mark update-all &&
+ update_repos &&
+ git remote update &&
+ repo_fetched one &&
+ repo_fetched two
+'
+
+test_expect_success 'nonexistant group produces error' '
+ mark nonexistant &&
+ update_repos &&
+ test_must_fail git remote update nonexistant &&
+ ! repo_fetched one &&
+ ! repo_fetched two
+'
+
+test_expect_success 'updating group updates all members' '
+ mark group-all &&
+ update_repos &&
+ git config --add remotes.all one &&
+ git config --add remotes.all two &&
+ git remote update all &&
+ repo_fetched one &&
+ repo_fetched two
+'
+
+test_expect_success 'updating group does not update non-members' '
+ mark group-some &&
+ update_repos &&
+ git config --add remotes.some one &&
+ git remote update some &&
+ repo_fetched one &&
+ ! repo_fetched two
+'
+
+test_expect_success 'updating remote name updates that remote' '
+ mark remote-name &&
+ update_repos &&
+ git remote update one &&
+ repo_fetched one &&
+ ! repo_fetched two
+'
+
+test_done
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index 052a6c90f..54b7ea650 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -506,6 +506,66 @@ test_expect_success 'optimized merge base checks' '
unset GIT_TRACE
'
+# This creates another side branch called "parallel" with some files
+# in some directories, to test bisecting with paths.
+#
+# We should have the following:
+#
+# P1-P2-P3-P4-P5-P6-P7
+# / / /
+# H1-H2-H3-H4-H5-H6-H7
+# \ \ \
+# S5-A \
+# \ \
+# S6-S7----B
+#
+test_expect_success '"parallel" side branch creation' '
+ git bisect reset &&
+ git checkout -b parallel $HASH1 &&
+ mkdir dir1 dir2 &&
+ add_line_into_file "1(para): line 1 on parallel branch" dir1/file1 &&
+ PARA_HASH1=$(git rev-parse --verify HEAD) &&
+ add_line_into_file "2(para): line 2 on parallel branch" dir2/file2 &&
+ PARA_HASH2=$(git rev-parse --verify HEAD) &&
+ add_line_into_file "3(para): line 3 on parallel branch" dir2/file3 &&
+ PARA_HASH3=$(git rev-parse --verify HEAD)
+ git merge -m "merge HASH4 and PARA_HASH3" "$HASH4" &&
+ PARA_HASH4=$(git rev-parse --verify HEAD)
+ add_line_into_file "5(para): add line on parallel branch" dir1/file1 &&
+ PARA_HASH5=$(git rev-parse --verify HEAD)
+ add_line_into_file "6(para): add line on parallel branch" dir2/file2 &&
+ PARA_HASH6=$(git rev-parse --verify HEAD)
+ git merge -m "merge HASH7 and PARA_HASH6" "$HASH7" &&
+ PARA_HASH7=$(git rev-parse --verify HEAD)
+'
+
+test_expect_success 'restricting bisection on one dir' '
+ git bisect reset &&
+ git bisect start HEAD $HASH1 -- dir1 &&
+ para1=$(git rev-parse --verify HEAD) &&
+ test "$para1" = "$PARA_HASH1" &&
+ git bisect bad > my_bisect_log.txt &&
+ grep "$PARA_HASH1 is first bad commit" my_bisect_log.txt
+'
+
+test_expect_success 'restricting bisection on one dir and a file' '
+ git bisect reset &&
+ git bisect start HEAD $HASH1 -- dir1 hello &&
+ para4=$(git rev-parse --verify HEAD) &&
+ test "$para4" = "$PARA_HASH4" &&
+ git bisect bad &&
+ hash3=$(git rev-parse --verify HEAD) &&
+ test "$hash3" = "$HASH3" &&
+ git bisect good &&
+ hash4=$(git rev-parse --verify HEAD) &&
+ test "$hash4" = "$HASH4" &&
+ git bisect good &&
+ para1=$(git rev-parse --verify HEAD) &&
+ test "$para1" = "$PARA_HASH1" &&
+ git bisect good > my_bisect_log.txt &&
+ grep "$PARA_HASH4 is first bad commit" my_bisect_log.txt
+'
+
#
#
test_done
diff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh
index 8bfae44a8..daf02d5c1 100755
--- a/t/t6300-for-each-ref.sh
+++ b/t/t6300-for-each-ref.sh
@@ -26,6 +26,13 @@ test_expect_success 'Create sample commit with known timestamp' '
git tag -a -m "Tagging at $datestamp" testtag
'
+test_expect_success 'Create upstream config' '
+ git update-ref refs/remotes/origin/master master &&
+ git remote add origin nowhere &&
+ git config branch.master.remote origin &&
+ git config branch.master.merge refs/heads/master
+'
+
test_atom() {
case "$1" in
head) ref=refs/heads/master ;;
@@ -39,6 +46,7 @@ test_atom() {
}
test_atom head refname refs/heads/master
+test_atom head upstream refs/remotes/origin/master
test_atom head objecttype commit
test_atom head objectsize 171
test_atom head objectname 67a36f10722846e891fbada1ba48ed035de75581
@@ -68,6 +76,7 @@ test_atom head contents 'Initial
'
test_atom tag refname refs/tags/testtag
+test_atom tag upstream ''
test_atom tag objecttype tag
test_atom tag objectsize 154
test_atom tag objectname 98b46b1d36e5b07909de1b3886224e3e81e87322
@@ -203,6 +212,7 @@ test_expect_success 'Check format "rfc2822" date fields output' '
cat >expected <<\EOF
refs/heads/master
+refs/remotes/origin/master
refs/tags/testtag
EOF
@@ -214,6 +224,7 @@ test_expect_success 'Verify ascending sort' '
cat >expected <<\EOF
refs/tags/testtag
+refs/remotes/origin/master
refs/heads/master
EOF
@@ -224,6 +235,7 @@ test_expect_success 'Verify descending sort' '
cat >expected <<\EOF
'refs/heads/master'
+'refs/remotes/origin/master'
'refs/tags/testtag'
EOF
@@ -244,6 +256,7 @@ test_expect_success 'Quoting style: python' '
cat >expected <<\EOF
"refs/heads/master"
+"refs/remotes/origin/master"
"refs/tags/testtag"
EOF
@@ -273,6 +286,15 @@ test_expect_success 'Check short refname format' '
test_cmp expected actual
'
+cat >expected <<EOF
+origin/master
+EOF
+
+test_expect_success 'Check short upstream format' '
+ git for-each-ref --format="%(upstream:short)" refs/heads >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'Check for invalid refname format' '
test_must_fail git for-each-ref --format="%(refname:INVALID)"
'
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index 0e21632f1..bdb808af1 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -171,7 +171,7 @@ test_expect_success 'checkout to detach HEAD' '
git checkout -f renamer && git clean -f &&
git checkout renamer^ 2>messages &&
(cat >messages.expect <<EOF
-Note: moving to "renamer^" which isn'"'"'t a local branch
+Note: moving to '\''renamer^'\'' which isn'\''t a local branch
If you want to create a new branch from this checkout, you may do so
(now or later) by using -b with the checkout command again. Example:
git checkout -b <new_branch_name>
diff --git a/t/t7405-submodule-merge.sh b/t/t7405-submodule-merge.sh
new file mode 100755
index 000000000..aa6c44ce4
--- /dev/null
+++ b/t/t7405-submodule-merge.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+test_description='merging with submodules'
+
+. ./test-lib.sh
+
+#
+# history
+#
+# a --- c
+# / \ /
+# root X
+# \ / \
+# b --- d
+#
+
+test_expect_success setup '
+
+ mkdir sub &&
+ (cd sub &&
+ git init &&
+ echo original > file &&
+ git add file &&
+ test_tick &&
+ git commit -m sub-root) &&
+ git add sub &&
+ test_tick &&
+ git commit -m root &&
+
+ git checkout -b a master &&
+ (cd sub &&
+ echo A > file &&
+ git add file &&
+ test_tick &&
+ git commit -m sub-a) &&
+ git add sub &&
+ test_tick &&
+ git commit -m a &&
+
+ git checkout -b b master &&
+ (cd sub &&
+ echo B > file &&
+ git add file &&
+ test_tick &&
+ git commit -m sub-b) &&
+ git add sub &&
+ test_tick &&
+ git commit -m b
+
+ git checkout -b c a &&
+ git merge -s ours b &&
+
+ git checkout -b d b &&
+ git merge -s ours a
+'
+
+test_expect_failure 'merging with modify/modify conflict' '
+
+ git checkout -b test1 a &&
+ test_must_fail git merge b &&
+ test -f .git/MERGE_MSG &&
+ git diff
+
+'
+
+test_expect_success 'merging with a modify/modify conflict between merge bases' '
+
+ git reset --hard HEAD &&
+ git checkout -b test2 c &&
+ git merge d
+
+'
+
+test_done
diff --git a/t/t7501-commit.sh b/t/t7501-commit.sh
index b4e2b4db8..e2ef53228 100755
--- a/t/t7501-commit.sh
+++ b/t/t7501-commit.sh
@@ -38,7 +38,7 @@ test_expect_success \
"echo King of the bongo >file &&
test_must_fail git commit -m foo -a file"
-test_expect_success \
+test_expect_success PERL \
"using paths with --interactive" \
"echo bong-o-bong >file &&
! (echo 7 | git commit -m foo --interactive file)"
@@ -119,7 +119,7 @@ test_expect_success \
"echo 'gak' >file && \
git commit -m 'author' --author 'Rubber Duck <rduck@convoy.org>' -a"
-test_expect_success \
+test_expect_success PERL \
"interactive add" \
"echo 7 | git commit --interactive | grep 'What now'"
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 3c90c4fc1..d9420e0a3 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -3,6 +3,11 @@
test_description='git send-email'
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping git send-email tests, perl not available'
+ test_done
+fi
+
PROG='git send-email'
test_expect_success \
'prepare reference tree' \
diff --git a/t/t9134-git-svn-ignore-paths.sh b/t/t9134-git-svn-ignore-paths.sh
index c4b5b8bcf..71fdc4a69 100755
--- a/t/t9134-git-svn-ignore-paths.sh
+++ b/t/t9134-git-svn-ignore-paths.sh
@@ -31,6 +31,22 @@ test_expect_success 'clone an SVN repository with ignored www directory' '
test_cmp expect expect2
'
+test_expect_success 'init+fetch an SVN repository with ignored www directory' '
+ git svn init "$svnrepo" c &&
+ ( cd c && git svn fetch --ignore-paths="^www" ) &&
+ rm expect2 &&
+ echo test_qqq > expect &&
+ for i in c/*/*.txt; do cat $i >> expect2; done &&
+ test_cmp expect expect2
+'
+
+test_expect_success 'verify ignore-paths config saved by clone' '
+ (
+ cd g &&
+ git config --get svn-remote.svn.ignore-paths | fgrep "www"
+ )
+'
+
test_expect_success 'SVN-side change outside of www' '
(
cd s &&
@@ -41,9 +57,20 @@ test_expect_success 'SVN-side change outside of www' '
)
'
-test_expect_success 'update git svn-cloned repo' '
+test_expect_success 'update git svn-cloned repo (config ignore)' '
(
cd g &&
+ git svn rebase &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'update git svn-cloned repo (option ignore)' '
+ (
+ cd c &&
git svn rebase --ignore-paths="^www" &&
printf "test_qqq\nb\n" > expect &&
for i in */*.txt; do cat $i >> expect2; done &&
@@ -62,9 +89,20 @@ test_expect_success 'SVN-side change inside of ignored www' '
)
'
-test_expect_success 'update git svn-cloned repo' '
+test_expect_success 'update git svn-cloned repo (config ignore)' '
(
cd g &&
+ git svn rebase &&
+ printf "test_qqq\nb\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'update git svn-cloned repo (option ignore)' '
+ (
+ cd c &&
git svn rebase --ignore-paths="^www" &&
printf "test_qqq\nb\n" > expect &&
for i in */*.txt; do cat $i >> expect2; done &&
@@ -84,9 +122,20 @@ test_expect_success 'SVN-side change in and out of ignored www' '
)
'
-test_expect_success 'update git svn-cloned repo again' '
+test_expect_success 'update git svn-cloned repo again (config ignore)' '
(
cd g &&
+ git svn rebase &&
+ printf "test_qqq\nb\nygg\n" > expect &&
+ for i in */*.txt; do cat $i >> expect2; done &&
+ test_cmp expect2 expect &&
+ rm expect expect2
+ )
+'
+
+test_expect_success 'update git svn-cloned repo again (option ignore)' '
+ (
+ cd c &&
git svn rebase --ignore-paths="^www" &&
printf "test_qqq\nb\nygg\n" > expect &&
for i in */*.txt; do cat $i >> expect2; done &&
diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh
index 36656923a..56b7c0692 100755
--- a/t/t9200-git-cvsexportcommit.sh
+++ b/t/t9200-git-cvsexportcommit.sh
@@ -6,6 +6,11 @@ test_description='Test export of commits to CVS'
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping git cvsexportcommit tests, perl not available'
+ test_done
+fi
+
cvs >/dev/null 2>&1
if test $? -ne 1
then
diff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh
index 39185db6c..64f947d75 100755
--- a/t/t9400-git-cvsserver-server.sh
+++ b/t/t9400-git-cvsserver-server.sh
@@ -10,6 +10,10 @@ cvs CLI client via git-cvsserver server'
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping git cvsserver tests, perl not available'
+ test_done
+fi
cvs >/dev/null 2>&1
if test $? -ne 1
then
diff --git a/t/t9401-git-cvsserver-crlf.sh b/t/t9401-git-cvsserver-crlf.sh
index 12e0e5082..aca40c1b1 100755
--- a/t/t9401-git-cvsserver-crlf.sh
+++ b/t/t9401-git-cvsserver-crlf.sh
@@ -52,6 +52,11 @@ then
say 'skipping git-cvsserver tests, cvs not found'
test_done
fi
+if ! test_have_prereq PERL
+then
+ say 'skipping git-cvsserver tests, perl not available'
+ test_done
+fi
perl -e 'use DBI; use DBD::SQLite' >/dev/null 2>&1 || {
say 'skipping git-cvsserver tests, Perl SQLite interface unavailable'
test_done
diff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh
index 0bd332c49..f4210fbb0 100755
--- a/t/t9500-gitweb-standalone-no-errors.sh
+++ b/t/t9500-gitweb-standalone-no-errors.sh
@@ -65,6 +65,11 @@ gitweb_run () {
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping gitweb tests, perl not available'
+ test_done
+fi
+
perl -MEncode -e 'decode_utf8("", Encode::FB_CROAK)' >/dev/null 2>&1 || {
say 'skipping gitweb tests, perl version is too old'
test_done
diff --git a/t/t9600-cvsimport.sh b/t/t9600-cvsimport.sh
index 33eb51938..4322a0c1e 100755
--- a/t/t9600-cvsimport.sh
+++ b/t/t9600-cvsimport.sh
@@ -3,6 +3,11 @@
test_description='git cvsimport basic tests'
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping git cvsimport tests, perl not available'
+ test_done
+fi
+
CVSROOT=$(pwd)/cvsroot
export CVSROOT
unset CVS_SERVER
diff --git a/t/t9700-perl-git.sh b/t/t9700-perl-git.sh
index 4a501c684..b4ca24462 100755
--- a/t/t9700-perl-git.sh
+++ b/t/t9700-perl-git.sh
@@ -6,6 +6,11 @@
test_description='perl interface (Git.pm)'
. ./test-lib.sh
+if ! test_have_prereq PERL; then
+ say 'skipping perl interface tests, perl not available'
+ test_done
+fi
+
perl -MTest::More -e 0 2>/dev/null || {
say "Perl Test::More unavailable, skipping test"
test_done
diff --git a/t/test-lib.sh b/t/test-lib.sh
index b050196cb..4bd986f43 100644
--- a/t/test-lib.sh
+++ b/t/test-lib.sh
@@ -698,6 +698,8 @@ case $(uname -s) in
;;
esac
+test -z "$NO_PERL" && test_set_prereq PERL
+
# test whether the filesystem supports symbolic links
ln -s x y 2>/dev/null && test -h y 2>/dev/null && test_set_prereq SYMLINKS
rm -f y
diff --git a/templates/hooks--update.sample b/templates/hooks--update.sample
index a3f68ae3b..f8bf490cf 100755
--- a/templates/hooks--update.sample
+++ b/templates/hooks--update.sample
@@ -16,6 +16,9 @@
# hooks.allowdeletebranch
# This boolean sets whether deleting branches will be allowed in the
# repository. By default they won't be.
+# hooks.denycreatebranch
+# This boolean sets whether remotely creating branches will be denied
+# in the repository. By default this is allowed.
#
# --- Command line
@@ -39,6 +42,7 @@ fi
# --- Config
allowunannotated=$(git config --bool hooks.allowunannotated)
allowdeletebranch=$(git config --bool hooks.allowdeletebranch)
+denycreatebranch=$(git config --bool hooks.denycreatebranch)
allowdeletetag=$(git config --bool hooks.allowdeletetag)
# check for no description
@@ -52,7 +56,8 @@ esac
# --- Check types
# if $newrev is 0000...0000, it's a commit to delete a ref.
-if [ "$newrev" = "0000000000000000000000000000000000000000" ]; then
+zero="0000000000000000000000000000000000000000"
+if [ "$newrev" = "$zero" ]; then
newrev_type=delete
else
newrev_type=$(git-cat-file -t $newrev)
@@ -80,6 +85,10 @@ case "$refname","$newrev_type" in
;;
refs/heads/*,commit)
# branch
+ if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then
+ echo "*** Creating a branch is not allowed in this repository" >&2
+ exit 1
+ fi
;;
refs/heads/*,delete)
# delete branch
diff --git a/tree-diff.c b/tree-diff.c
index 9f67af6c1..b05d0f435 100644
--- a/tree-diff.c
+++ b/tree-diff.c
@@ -118,10 +118,16 @@ static int tree_entry_interesting(struct tree_desc *desc, const char *base, int
continue;
/*
- * The base is a subdirectory of a path which
- * was specified, so all of them are interesting.
+ * If the base is a subdirectory of a path which
+ * was specified, all of them are interesting.
*/
- return 2;
+ if (!matchlen ||
+ base[matchlen] == '/' ||
+ match[matchlen - 1] == '/')
+ return 2;
+
+ /* Just a random prefix match */
+ continue;
}
/* Does the base match? */
diff --git a/tree.c b/tree.c
index 25d2e29fa..0d703a0c4 100644
--- a/tree.c
+++ b/tree.c
@@ -60,8 +60,12 @@ static int match_tree_entry(const char *base, int baselen, const char *path, uns
/* If it doesn't match, move along... */
if (strncmp(base, match, matchlen))
continue;
- /* The base is a subdirectory of a path which was specified. */
- return 1;
+ /* pathspecs match only at the directory boundaries */
+ if (!matchlen ||
+ base[matchlen] == '/' ||
+ match[matchlen - 1] == '/')
+ return 1;
+ continue;
}
/* Does the base match? */
diff --git a/unimplemented.sh b/unimplemented.sh
new file mode 100644
index 000000000..5252de4b2
--- /dev/null
+++ b/unimplemented.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+
+echo >&2 "fatal: git was built without support for `basename $0` (@@REASON@@)."
+exit 128
diff --git a/upload-pack.c b/upload-pack.c
index a49d87244..495c99f80 100644
--- a/upload-pack.c
+++ b/upload-pack.c
@@ -66,7 +66,7 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
}
static FILE *pack_pipe = NULL;
-static void show_commit(struct commit *commit)
+static void show_commit(struct commit *commit, void *data)
{
if (commit->object.flags & BOUNDARY)
fputc('-', pack_pipe);
@@ -78,7 +78,7 @@ static void show_commit(struct commit *commit)
commit->buffer = NULL;
}
-static void show_object(struct object_array_entry *p)
+static void show_object(struct object_array_entry *p, void *data)
{
/* An object with name "foo\n0000000..." can be used to
* confuse downstream git-pack-objects very badly.
@@ -134,7 +134,7 @@ static int do_rev_list(int fd, void *create_full_pack)
if (prepare_revision_walk(&revs))
die("revision walk setup failed");
mark_edges_uninteresting(revs.commits, &revs, show_edge);
- traverse_commit_list(&revs, show_commit, show_object);
+ traverse_commit_list(&revs, show_commit, show_object, NULL);
fflush(pack_pipe);
fclose(pack_pipe);
return 0;