aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/RelNotes-1.5.4.3.txt27
-rw-r--r--Documentation/config.txt27
-rw-r--r--Documentation/diff-options.txt8
-rw-r--r--Documentation/git-am.txt2
-rw-r--r--Documentation/git-branch.txt32
-rw-r--r--Documentation/git-bundle.txt44
-rw-r--r--Documentation/git-checkout.txt24
-rw-r--r--Documentation/git-describe.txt5
-rw-r--r--Documentation/git-filter-branch.txt15
-rw-r--r--Documentation/git-format-patch.txt25
-rw-r--r--Documentation/git-pack-objects.txt2
-rw-r--r--Documentation/git-rev-list.txt1
-rw-r--r--Documentation/git.txt3
-rw-r--r--Documentation/rev-list-options.txt5
-rw-r--r--Documentation/urls.txt23
-rwxr-xr-xGIT-VERSION-GEN3
-rw-r--r--Makefile19
-rw-r--r--alias.c22
-rw-r--r--branch.c152
-rw-r--r--branch.h24
-rw-r--r--builtin-apply.c719
-rw-r--r--builtin-branch.c151
-rw-r--r--builtin-checkout.c569
-rw-r--r--builtin-commit.c3
-rw-r--r--builtin-describe.c35
-rw-r--r--builtin-diff.c11
-rwxr-xr-xbuiltin-fast-export.c2
-rw-r--r--builtin-fetch-pack.c4
-rw-r--r--builtin-for-each-ref.c2
-rw-r--r--builtin-init-db.c21
-rw-r--r--builtin-log.c342
-rw-r--r--builtin-merge-file.c2
-rw-r--r--builtin-merge-recursive.c (renamed from merge-recursive.c)59
-rw-r--r--builtin-pack-objects.c14
-rw-r--r--builtin-push.c9
-rw-r--r--builtin-read-tree.c4
-rw-r--r--builtin-reflog.c5
-rw-r--r--builtin-rerere.c19
-rw-r--r--builtin-reset.c20
-rw-r--r--builtin-rev-parse.c2
-rw-r--r--builtin-send-pack.c15
-rw-r--r--builtin-shortlog.c149
-rw-r--r--builtin-tag.c3
-rw-r--r--builtin-verify-tag.c2
-rw-r--r--builtin.h2
-rw-r--r--bundle.c6
-rw-r--r--cache.h52
-rw-r--r--commit.h15
-rw-r--r--config.c8
-rwxr-xr-xcontrib/completion/git-completion.bash5
-rwxr-xr-xcontrib/examples/git-checkout.sh (renamed from git-checkout.sh)11
-rw-r--r--copy.c21
-rw-r--r--date.c6
-rw-r--r--diff-lib.c6
-rw-r--r--diff.c207
-rw-r--r--diff.h6
-rw-r--r--environment.c1
-rwxr-xr-xgit-bisect.sh14
-rw-r--r--git-gui/Makefile18
-rwxr-xr-xgit-gui/git-gui.sh2
-rw-r--r--git-gui/lib/choose_repository.tcl4
-rw-r--r--git-gui/lib/error.tcl22
-rwxr-xr-xgit-pull.sh3
-rw-r--r--git.c20
-rwxr-xr-xgitweb/gitweb.perl73
-rw-r--r--hash-object.c12
-rw-r--r--help.c152
-rw-r--r--log-tree.c126
-rw-r--r--log-tree.h2
-rw-r--r--merge-recursive.h20
-rw-r--r--pretty.c32
-rw-r--r--read-cache.c40
-rw-r--r--receive-pack.c2
-rw-r--r--refs.c27
-rw-r--r--remote.c247
-rw-r--r--remote.h4
-rw-r--r--revision.c14
-rw-r--r--revision.h2
-rw-r--r--run-command.c28
-rw-r--r--run-command.h20
-rw-r--r--shortlog.h26
-rwxr-xr-xt/t0050-filesystem.sh93
-rwxr-xr-xt/t1502-rev-parse-parseopt.sh43
-rwxr-xr-xt/t3200-branch.sh22
-rwxr-xr-xt/t4013-diff-various.sh1
-rw-r--r--t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^100
-rwxr-xr-xt/t4014-format-patch.sh113
-rwxr-xr-xt/t4019-diff-wserror.sh40
-rwxr-xr-xt/t4105-apply-fuzz.sh57
-rwxr-xr-xt/t4125-apply-ws-fuzz.sh103
-rwxr-xr-xt/t5303-hash-object.sh35
-rwxr-xr-xt/t5516-fetch-push.sh74
-rwxr-xr-xt/t6023-merge-file.sh20
-rwxr-xr-xt/t6029-merge-subtree.sh32
-rwxr-xr-xt/t6030-bisect-porcelain.sh2
-rwxr-xr-xt/t7201-co.sh83
-rwxr-xr-xt/t9001-send-email.sh51
-rw-r--r--thread-utils.c48
-rw-r--r--thread-utils.h6
-rw-r--r--unpack-trees.c158
-rw-r--r--unpack-trees.h2
-rw-r--r--ws.c119
-rw-r--r--xdiff/xdiff.h1
-rw-r--r--xdiff/xmerge.c70
104 files changed, 4002 insertions, 1152 deletions
diff --git a/Documentation/RelNotes-1.5.4.3.txt b/Documentation/RelNotes-1.5.4.3.txt
new file mode 100644
index 000000000..b0fc67fb2
--- /dev/null
+++ b/Documentation/RelNotes-1.5.4.3.txt
@@ -0,0 +1,27 @@
+GIT v1.5.4.3 Release Notes
+==========================
+
+Fixes since v1.5.4.2
+--------------------
+
+ * RPM spec used to pull in everything with 'git'. This has been
+ changed so that 'git' package contains just the core parts,
+ and we now supply 'git-all' metapackage to slurp in everything.
+ This should match end user's expectation better.
+
+ * When some refs failed to update, git-push reported "failure"
+ which was unclear if some other refs were updated or all of
+ them failed atomically (the answer is the former). Reworded
+ the message to clarify this.
+
+ * "git clone" from a repository whose HEAD was misconfigured
+ did not set up the remote properly. Now it tries to do
+ better.
+
+ * Updated git-push documentation to clarify what "matching"
+ means, in order to reduce user confusion.
+
+ * Updated git-add documentation to clarify "add -u" operates in
+ the current subdirectory you are in, just like other commands.
+
+ * git-gui updates to work on OSX and Windows better.
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 7b676710b..4027726f2 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -353,6 +353,10 @@ core.whitespace::
error (enabled by default).
* `indent-with-non-tab` treats a line that is indented with 8 or more
space characters as an error (not enabled by default).
+* `cr-at-eol` treats a carriage-return at the end of line as
+ part of the line terminator, i.e. with it, `trailing-space`
+ does not trigger if the character before such a carriage-return
+ is not a whitespace (not enabled by default).
alias.*::
Command aliases for the linkgit:git[1] command wrapper - e.g.
@@ -375,10 +379,14 @@ apply.whitespace::
branch.autosetupmerge::
Tells `git-branch` and `git-checkout` to setup new branches
- so that linkgit:git-pull[1] will appropriately merge from that
- remote branch. Note that even if this option is not set,
+ so that linkgit:git-pull[1] will appropriately merge from the
+ starting point branch. Note that even if this option is not set,
this behavior can be chosen per-branch using the `--track`
- and `--no-track` options. This option defaults to true.
+ and `--no-track` options. The valid settings are: `false` -- no
+ automatic setup is done; `true` -- automatic setup is done when the
+ starting point is a remote branch; `always` -- automatic setup is
+ done when the starting point is either a local branch or remote
+ branch. This option defaults to true.
branch.<name>.remote::
When in branch <name>, it tells `git fetch` which remote to fetch.
@@ -808,6 +816,8 @@ pack.threads::
warning. This is meant to reduce packing time on multiprocessor
machines. The required amount of memory for the delta search window
is however multiplied by the number of threads.
+ Specifying 0 will cause git to auto-detect the number of CPU's
+ and set the number of threads accordingly.
pack.indexVersion::
Specify the default pack index version. Valid values are 1 for
@@ -893,6 +903,17 @@ tar.umask::
archiving user's umask will be used instead. See umask(2) and
linkgit:git-archive[1].
+url.<base>.insteadOf::
+ Any URL that starts with this value will be rewritten to
+ start, instead, with <base>. In cases where some site serves a
+ large number of repositories, and serves them with multiple
+ access methods, and some users need to use different access
+ methods, this feature allows people to specify any of the
+ equivalent URLs and have git automatically rewrite the URL to
+ the best alternative for the particular user, even for a
+ never-before-seen repository on the site. When more than one
+ insteadOf strings match a given URL, the longest match is used.
+
user.email::
Your email address to be recorded in any newly created commits.
Can be overridden by the 'GIT_AUTHOR_EMAIL', 'GIT_COMMITTER_EMAIL', and
diff --git a/Documentation/diff-options.txt b/Documentation/diff-options.txt
index 8d35cbd60..8dc5b001c 100644
--- a/Documentation/diff-options.txt
+++ b/Documentation/diff-options.txt
@@ -170,6 +170,14 @@ endif::git-format-patch[]
Swap two inputs; that is, show differences from index or
on-disk file to tree contents.
+--relative[=<path>]::
+ When run from a subdirectory of the project, it can be
+ told to exclude changes outside the directory and show
+ pathnames relative to it with this option. When you are
+ not in a subdirectory (e.g. in a bare repository), you
+ can name which subdirectory to make the output relative
+ to by giving a <path> as an argument.
+
--text::
Treat all files as text.
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt
index 2ffba2102..e640fc75c 100644
--- a/Documentation/git-am.txt
+++ b/Documentation/git-am.txt
@@ -138,7 +138,7 @@ aborts in the middle,. You can recover from this in one of two ways:
The command refuses to process new mailboxes while `.dotest`
directory exists, so if you decide to start over from scratch,
-run `rm -f .dotest` before running the command with mailbox
+run `rm -f -r .dotest` before running the command with mailbox
names.
diff --git a/Documentation/git-branch.txt b/Documentation/git-branch.txt
index 7e8874aca..6f07a17a2 100644
--- a/Documentation/git-branch.txt
+++ b/Documentation/git-branch.txt
@@ -35,11 +35,10 @@ working tree to it; use "git checkout <newbranch>" to switch to the
new branch.
When a local branch is started off a remote branch, git sets up the
-branch so that linkgit:git-pull[1] will appropriately merge from that
-remote branch. If this behavior is not desired, it is possible to
-disable it using the global `branch.autosetupmerge` configuration
-flag. That setting can be overridden by using the `--track`
-and `--no-track` options.
+branch so that linkgit:git-pull[1] will appropriately merge from
+the remote branch. This behavior may be changed via the global
+`branch.autosetupmerge` configuration flag. That setting can be
+overridden by using the `--track` and `--no-track` options.
With a '-m' or '-M' option, <oldbranch> will be renamed to <newbranch>.
If <oldbranch> had a corresponding reflog, it is renamed to match
@@ -105,20 +104,19 @@ OPTIONS
Display the full sha1s in output listing rather than abbreviating them.
--track::
- Set up configuration so that git-pull will automatically
- retrieve data from the remote branch. Use this if you always
- pull from the same remote branch into the new branch, or if you
- don't want to use "git pull <repository> <refspec>" explicitly.
- This behavior is the default. Set the
- branch.autosetupmerge configuration variable to false if you
- want git-checkout and git-branch to always behave as if
- '--no-track' were given.
+ When creating a new branch, set up configuration so that git-pull
+ will automatically retrieve data from the start point, which must be
+ a branch. Use this if you always pull from the same upstream branch
+ into the new branch, and if you don't want to use "git pull
+ <repository> <refspec>" explicitly. This behavior is the default
+ when the start point is a remote branch. Set the
+ branch.autosetupmerge configuration variable to `false` if you want
+ git-checkout and git-branch to always behave as if '--no-track' were
+ given. Set it to `always` if you want this behavior when the
+ start-point is either a local or remote branch.
--no-track::
- When a branch is created off a remote branch,
- set up configuration so that git-pull will not retrieve data
- from the remote branch, ignoring the branch.autosetupmerge
- configuration variable.
+ Ignore the branch.autosetupmerge configuration variable.
<branchname>::
The name of the branch to create or delete.
diff --git a/Documentation/git-bundle.txt b/Documentation/git-bundle.txt
index 72f080a97..505ac056e 100644
--- a/Documentation/git-bundle.txt
+++ b/Documentation/git-bundle.txt
@@ -99,36 +99,62 @@ Assume two repositories exist as R1 on machine A, and R2 on machine B.
For whatever reason, direct connection between A and B is not allowed,
but we can move data from A to B via some mechanism (CD, email, etc).
We want to update R2 with developments made on branch master in R1.
+
+To create the bundle you have to specify the basis. You have some options:
+
+- Without basis.
++
+This is useful when sending the whole history.
+
+------------
+$ git bundle create mybundle master
+------------
+
+- Using temporally tags.
++
We set a tag in R1 (lastR2bundle) after the previous such transport,
and move it afterwards to help build the bundle.
-in R1 on A:
-
------------
$ git-bundle create mybundle master ^lastR2bundle
$ git tag -f lastR2bundle master
------------
-(move mybundle from A to B by some mechanism)
+- Using a tag present in both repositories
+
+------------
+$ git bundle create mybundle master ^v1.0.0
+------------
+
+- A basis based on time.
+
+------------
+$ git bundle create mybundle master --since=10.days.ago
+------------
-in R2 on B:
+- With a limit on the number of commits
------------
-$ git-bundle verify mybundle
-$ git-fetch mybundle refspec
+$ git bundle create mybundle master -n 10
------------
-where refspec is refInBundle:localRef
+Then you move mybundle from A to B, and in R2 on B:
+------------
+$ git-bundle verify mybundle
+$ git-fetch mybundle master:localRef
+------------
-Also, with something like this in your config:
+With something like this in the config in R2:
+------------------------
[remote "bundle"]
url = /home/me/tmp/file.bdl
fetch = refs/heads/*:refs/remotes/origin/*
+------------------------
You can first sneakernet the bundle file to ~/tmp/file.bdl and
-then these commands:
+then these commands on machine B:
------------
$ git ls-remote bundle
diff --git a/Documentation/git-checkout.txt b/Documentation/git-checkout.txt
index b4cfa044b..4014e7256 100644
--- a/Documentation/git-checkout.txt
+++ b/Documentation/git-checkout.txt
@@ -48,21 +48,19 @@ OPTIONS
may restrict the characters allowed in a branch name.
--track::
- When -b is given and a branch is created off a remote branch,
- set up configuration so that git-pull will automatically
- retrieve data from the remote branch. Use this if you always
- pull from the same remote branch into the new branch, or if you
- don't want to use "git pull <repository> <refspec>" explicitly.
- This behavior is the default. Set the
- branch.autosetupmerge configuration variable to false if you
- want git-checkout and git-branch to always behave as if
- '--no-track' were given.
+ When creating a new branch, set up configuration so that git-pull
+ will automatically retrieve data from the start point, which must be
+ a branch. Use this if you always pull from the same upstream branch
+ into the new branch, and if you don't want to use "git pull
+ <repository> <refspec>" explicitly. This behavior is the default
+ when the start point is a remote branch. Set the
+ branch.autosetupmerge configuration variable to `false` if you want
+ git-checkout and git-branch to always behave as if '--no-track' were
+ given. Set it to `always` if you want this behavior when the
+ start-point is either a local or remote branch.
--no-track::
- When -b is given and a branch is created off a remote branch,
- set up configuration so that git-pull will not retrieve data
- from the remote branch, ignoring the branch.autosetupmerge
- configuration variable.
+ Ignore the branch.autosetupmerge configuration variable.
-l::
Create the new branch's reflog. This activates recording of
diff --git a/Documentation/git-describe.txt b/Documentation/git-describe.txt
index 1c3dfb40c..fbb40a291 100644
--- a/Documentation/git-describe.txt
+++ b/Documentation/git-describe.txt
@@ -45,6 +45,11 @@ OPTIONS
candidates to describe the input committish consider
up to <n> candidates. Increasing <n> above 10 will take
slightly longer but may produce a more accurate result.
+ An <n> of 0 will cause only exact matches to be output.
+
+--exact-match::
+ Only output exact matches (a tag directly references the
+ supplied commit). This is a synonym for --candidates=0.
--debug::
Verbosely display information about the searching strategy
diff --git a/Documentation/git-filter-branch.txt b/Documentation/git-filter-branch.txt
index e22dfa580..543a1cf10 100644
--- a/Documentation/git-filter-branch.txt
+++ b/Documentation/git-filter-branch.txt
@@ -56,7 +56,9 @@ notable exception of the commit filter, for technical reasons).
Prior to that, the $GIT_COMMIT environment variable will be set to contain
the id of the commit being rewritten. Also, GIT_AUTHOR_NAME,
GIT_AUTHOR_EMAIL, GIT_AUTHOR_DATE, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL,
-and GIT_COMMITTER_DATE are set according to the current commit.
+and GIT_COMMITTER_DATE are set according to the current commit. If any
+evaluation of <command> returns a non-zero exit status, the whole operation
+will be aborted.
A 'map' function is available that takes an "original sha1 id" argument
and outputs a "rewritten sha1 id" if the commit has been already
@@ -197,7 +199,7 @@ happened). If this is not the case, use:
--------------------------------------------------------------------------
git filter-branch --parent-filter \
- 'cat; test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>"' HEAD
+ 'test $GIT_COMMIT = <commit-id> && echo "-p <graft-id>" || cat' HEAD
--------------------------------------------------------------------------
or even simpler:
@@ -240,6 +242,15 @@ committed a merge between P1 and P2, it will be propagated properly
and all children of the merge will become merge commits with P1,P2
as their parents instead of the merge commit.
+You can rewrite the commit log messages using `--message-filter`. For
+example, `git-svn-id` strings in a repository created by `git-svn` can
+be removed this way:
+
+-------------------------------------------------------
+git filter-branch --message-filter '
+ sed -e "/^git-svn-id:/d"
+'
+-------------------------------------------------------
To restrict rewriting to only part of the history, specify a revision
range in addition to the new branch name. The new branch name will
diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt
index 651efe6ca..b5207b760 100644
--- a/Documentation/git-format-patch.txt
+++ b/Documentation/git-format-patch.txt
@@ -10,13 +10,15 @@ SYNOPSIS
--------
[verse]
'git-format-patch' [-k] [-o <dir> | --stdout] [--thread]
- [--attach[=<boundary>] | --inline[=<boundary>]]
- [-s | --signoff] [<common diff options>]
- [-n | --numbered | -N | --no-numbered]
- [--start-number <n>] [--numbered-files]
- [--in-reply-to=Message-Id] [--suffix=.<sfx>]
- [--ignore-if-in-upstream]
- [--subject-prefix=Subject-Prefix]
+ [--attach[=<boundary>] | --inline[=<boundary>]]
+ [-s | --signoff] [<common diff options>]
+ [-n | --numbered | -N | --no-numbered]
+ [--start-number <n>] [--numbered-files]
+ [--in-reply-to=Message-Id] [--suffix=.<sfx>]
+ [--ignore-if-in-upstream]
+ [--subject-prefix=Subject-Prefix]
+ [--cc=<email>]
+ [--cover-letter]
[ <since> | <revision range> ]
DESCRIPTION
@@ -135,6 +137,15 @@ include::diff-options.txt[]
allows for useful naming of a patch series, and can be
combined with the --numbered option.
+--cc=<email>::
+ Add a "Cc:" header to the email headers. This is in addition
+ to any configured headers, and may be used multiple times.
+
+--cover-letter::
+ Generate a cover letter template. You still have to fill in
+ a description, but the shortlog and the diffstat will be
+ generated for you.
+
--suffix=.<sfx>::
Instead of using `.patch` as the suffix for generated
filenames, use specified suffix. A common alternative is
diff --git a/Documentation/git-pack-objects.txt b/Documentation/git-pack-objects.txt
index 8353be186..5c1bd3b08 100644
--- a/Documentation/git-pack-objects.txt
+++ b/Documentation/git-pack-objects.txt
@@ -177,6 +177,8 @@ base-name::
This is meant to reduce packing time on multiprocessor machines.
The required amount of memory for the delta search window is
however multiplied by the number of threads.
+ Specifying 0 will cause git to auto-detect the number of CPU's
+ and set the number of threads accordingly.
--index-version=<version>[,<offset>]::
This is intended to be used by the test suite only. It allows
diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt
index 5b96eabfc..a8d489f9f 100644
--- a/Documentation/git-rev-list.txt
+++ b/Documentation/git-rev-list.txt
@@ -31,6 +31,7 @@ SYNOPSIS
[ \--(author|committer|grep)=<pattern> ]
[ \--regexp-ignore-case | \-i ]
[ \--extended-regexp | \-E ]
+ [ \--fixed-strings | \-F ]
[ \--date={local|relative|default|iso|rfc|short} ]
[ [\--objects | \--objects-edge] [ \--unpacked ] ]
[ \--pretty | \--header ]
diff --git a/Documentation/git.txt b/Documentation/git.txt
index d57bed618..741ae0e4c 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -43,9 +43,10 @@ unreleased) version of git, that is available from 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v1.5.4.2/git.html[documentation for release 1.5.4.2]
+* link:v1.5.4.3/git.html[documentation for release 1.5.4.3]
* release notes for
+ link:RelNotes-1.5.4.3.txt[1.5.4.3],
link:RelNotes-1.5.4.2.txt[1.5.4.2],
link:RelNotes-1.5.4.1.txt[1.5.4.1],
link:RelNotes-1.5.4.txt[1.5.4].
diff --git a/Documentation/rev-list-options.txt b/Documentation/rev-list-options.txt
index a8138e27a..259072c07 100644
--- a/Documentation/rev-list-options.txt
+++ b/Documentation/rev-list-options.txt
@@ -153,6 +153,11 @@ limiting may be applied.
Consider the limiting patterns to be extended regular expressions
instead of the default basic regular expressions.
+-F, --fixed-strings::
+
+ Consider the limiting patterns to be fixed strings (don't interpret
+ pattern as a regular expression).
+
--remove-empty::
Stop when a given path disappears from the tree.
diff --git a/Documentation/urls.txt b/Documentation/urls.txt
index 81ac17f32..fa34c6747 100644
--- a/Documentation/urls.txt
+++ b/Documentation/urls.txt
@@ -44,3 +44,26 @@ endif::git-clone[]
ifdef::git-clone[]
They are equivalent, except the former implies --local option.
endif::git-clone[]
+
+
+If there are a large number of similarly-named remote repositories and
+you want to use a different format for them (such that the URLs you
+use will be rewritten into URLs that work), you can create a
+configuration section of the form:
+
+------------
+ [url "<actual url base>"]
+ insteadOf = <other url base>
+------------
+
+For example, with this:
+
+------------
+ [url "git://git.host.xz/"]
+ insteadOf = host.xz:/path/to/
+ insteadOf = work:
+------------
+
+a URL like "work:repo.git" or like "host.xz:/path/to/repo.git" will be
+rewritten in any context that takes a URL to be "git://git.host.xz/repo.git".
+
diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN
index 1ad324e23..6ddf04d21 100755
--- a/GIT-VERSION-GEN
+++ b/GIT-VERSION-GEN
@@ -16,7 +16,8 @@ elif test -d .git &&
case "$VN" in
*$LF*) (exit 1) ;;
v[0-9]*)
- git diff-index --quiet HEAD || VN="$VN-dirty" ;;
+ test -z "$(git diff-index --name-only HEAD)" ||
+ VN="$VN-dirty" ;;
esac
then
VN=$(echo "$VN" | sed -e 's/-/./g');
diff --git a/Makefile b/Makefile
index d33a556ff..a5b6eebf1 100644
--- a/Makefile
+++ b/Makefile
@@ -226,7 +226,7 @@ BASIC_CFLAGS =
BASIC_LDFLAGS =
SCRIPT_SH = \
- git-bisect.sh git-checkout.sh \
+ git-bisect.sh \
git-clone.sh \
git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
git-pull.sh git-rebase.sh git-rebase--interactive.sh \
@@ -265,23 +265,23 @@ PROGRAMS = \
git-upload-pack$X \
git-pack-redundant$X git-var$X \
git-merge-tree$X git-imap-send$X \
- git-merge-recursive$X \
$(EXTRA_PROGRAMS)
# Empty...
EXTRA_PROGRAMS =
+# List built-in command $C whose implementation cmd_$C() is not in
+# builtin-$C.o but is linked in as part of some other command.
BUILT_INS = \
git-format-patch$X git-show$X git-whatchanged$X git-cherry$X \
git-get-tar-commit-id$X git-init$X git-repo-config$X \
git-fsck-objects$X git-cherry-pick$X git-peek-remote$X git-status$X \
+ git-merge-subtree$X \
$(patsubst builtin-%.o,git-%$X,$(BUILTIN_OBJS))
# what 'all' will build and 'install' will install, in gitexecdir
ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
-ALL_PROGRAMS += git-merge-subtree$X
-
# what 'all' will build but not install in gitexecdir
OTHER_PROGRAMS = git$X gitweb/gitweb.cgi
@@ -327,7 +327,8 @@ LIB_OBJS = \
alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \
color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \
convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o \
- transport.o bundle.o walker.o parse-options.o ws.o archive.o
+ transport.o bundle.o walker.o parse-options.o ws.o archive.o branch.o \
+ alias.o
BUILTIN_OBJS = \
builtin-add.o \
@@ -339,6 +340,7 @@ BUILTIN_OBJS = \
builtin-bundle.o \
builtin-cat-file.o \
builtin-check-attr.o \
+ builtin-checkout.o \
builtin-checkout-index.o \
builtin-check-ref-format.o \
builtin-clean.o \
@@ -369,6 +371,7 @@ BUILTIN_OBJS = \
builtin-merge-base.o \
builtin-merge-file.o \
builtin-merge-ours.o \
+ builtin-merge-recursive.o \
builtin-mv.o \
builtin-name-rev.o \
builtin-pack-objects.o \
@@ -741,6 +744,7 @@ endif
ifdef THREADED_DELTA_SEARCH
BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
EXTLIBS += -lpthread
+ LIB_OBJS += thread-utils.o
endif
ifeq ($(TCLTK_PATH),)
@@ -838,9 +842,6 @@ help.o: help.c common-cmds.h GIT-CFLAGS
'-DGIT_MAN_PATH="$(mandir_SQ)"' \
'-DGIT_INFO_PATH="$(infodir_SQ)"' $<
-git-merge-subtree$X: git-merge-recursive$X
- $(QUIET_BUILT_IN)$(RM) $@ && ln git-merge-recursive$X $@
-
$(BUILT_INS): git$X
$(QUIET_BUILT_IN)$(RM) $@ && ln git$X $@
@@ -1102,7 +1103,7 @@ git.spec: git.spec.in
mv $@+ $@
GIT_TARNAME=git-$(GIT_VERSION)
-dist: git.spec git-archive configure
+dist: git.spec git-archive$(X) configure
./git-archive --format=tar \
--prefix=$(GIT_TARNAME)/ HEAD^{tree} > $(GIT_TARNAME).tar
@mkdir -p $(GIT_TARNAME)
diff --git a/alias.c b/alias.c
new file mode 100644
index 000000000..116cac87c
--- /dev/null
+++ b/alias.c
@@ -0,0 +1,22 @@
+#include "cache.h"
+
+static const char *alias_key;
+static char *alias_val;
+static int alias_lookup_cb(const char *k, const char *v)
+{
+ if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) {
+ if (!v)
+ return config_error_nonbool(k);
+ alias_val = xstrdup(v);
+ return 0;
+ }
+ return 0;
+}
+
+char *alias_lookup(const char *alias)
+{
+ alias_key = alias;
+ alias_val = NULL;
+ git_config(alias_lookup_cb);
+ return alias_val;
+}
diff --git a/branch.c b/branch.c
new file mode 100644
index 000000000..daf862e72
--- /dev/null
+++ b/branch.c
@@ -0,0 +1,152 @@
+#include "cache.h"
+#include "branch.h"
+#include "refs.h"
+#include "remote.h"
+#include "commit.h"
+
+struct tracking {
+ struct refspec spec;
+ char *src;
+ const char *remote;
+ int matches;
+};
+
+static int find_tracked_branch(struct remote *remote, void *priv)
+{
+ struct tracking *tracking = priv;
+
+ if (!remote_find_tracking(remote, &tracking->spec)) {
+ if (++tracking->matches == 1) {
+ tracking->src = tracking->spec.src;
+ tracking->remote = remote->name;
+ } else {
+ free(tracking->spec.src);
+ if (tracking->src) {
+ free(tracking->src);
+ tracking->src = NULL;
+ }
+ }
+ tracking->spec.src = NULL;
+ }
+
+ return 0;
+}
+
+/*
+ * This is called when new_ref is branched off of orig_ref, and tries
+ * to infer the settings for branch.<new_ref>.{remote,merge} from the
+ * config.
+ */
+static int setup_tracking(const char *new_ref, const char *orig_ref,
+ enum branch_track track)
+{
+ char key[1024];
+ struct tracking tracking;
+
+ if (strlen(new_ref) > 1024 - 7 - 7 - 1)
+ return error("Tracking not set up: name too long: %s",
+ new_ref);
+
+ memset(&tracking, 0, sizeof(tracking));
+ tracking.spec.dst = (char *)orig_ref;
+ if (for_each_remote(find_tracked_branch, &tracking))
+ return 1;
+
+ if (!tracking.matches)
+ switch (track) {
+ case BRANCH_TRACK_ALWAYS:
+ case BRANCH_TRACK_EXPLICIT:
+ break;
+ default:
+ return 1;
+ }
+
+ if (tracking.matches > 1)
+ return error("Not tracking: ambiguous information for ref %s",
+ orig_ref);
+
+ sprintf(key, "branch.%s.remote", new_ref);
+ git_config_set(key, tracking.remote ? tracking.remote : ".");
+ sprintf(key, "branch.%s.merge", new_ref);
+ git_config_set(key, tracking.src ? tracking.src : orig_ref);
+ free(tracking.src);
+ printf("Branch %s set up to track %s branch %s.\n", new_ref,
+ tracking.remote ? "remote" : "local", orig_ref);
+
+ return 0;
+}
+
+void create_branch(const char *head,
+ const char *name, const char *start_name,
+ int force, int reflog, enum branch_track track)
+{
+ struct ref_lock *lock;
+ struct commit *commit;
+ unsigned char sha1[20];
+ char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
+ int forcing = 0;
+
+ snprintf(ref, sizeof ref, "refs/heads/%s", name);
+ if (check_ref_format(ref))
+ die("'%s' is not a valid branch name.", name);
+
+ if (resolve_ref(ref, sha1, 1, NULL)) {
+ if (!force)
+ die("A branch named '%s' already exists.", name);
+ else if (!is_bare_repository() && !strcmp(head, name))
+ die("Cannot force update the current branch.");
+ forcing = 1;
+ }
+
+ real_ref = NULL;
+ if (get_sha1(start_name, sha1))
+ die("Not a valid object name: '%s'.", start_name);
+
+ switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
+ case 0:
+ /* Not branching from any existing branch */
+ if (track == BRANCH_TRACK_EXPLICIT)
+ die("Cannot setup tracking information; starting point is not a branch.");
+ break;
+ case 1:
+ /* Unique completion -- good */
+ break;
+ default:
+ die("Ambiguous object name: '%s'.", start_name);
+ break;
+ }
+
+ if ((commit = lookup_commit_reference(sha1)) == NULL)
+ die("Not a valid branch point: '%s'.", start_name);
+ hashcpy(sha1, commit->object.sha1);
+
+ lock = lock_any_ref_for_update(ref, NULL, 0);
+ if (!lock)
+ die("Failed to lock ref for update: %s.", strerror(errno));
+
+ if (reflog)
+ log_all_ref_updates = 1;
+
+ if (forcing)
+ snprintf(msg, sizeof msg, "branch: Reset from %s",
+ start_name);
+ else
+ snprintf(msg, sizeof msg, "branch: Created from %s",
+ start_name);
+
+ if (real_ref && track)
+ setup_tracking(name, real_ref, track);
+
+ if (write_ref_sha1(lock, sha1, msg) < 0)
+ die("Failed to write ref: %s.", strerror(errno));
+
+ free(real_ref);
+}
+
+void remove_branch_state(void)
+{
+ unlink(git_path("MERGE_HEAD"));
+ unlink(git_path("rr-cache/MERGE_RR"));
+ unlink(git_path("MERGE_MSG"));
+ unlink(git_path("SQUASH_MSG"));
+}
diff --git a/branch.h b/branch.h
new file mode 100644
index 000000000..9f0c2a2c1
--- /dev/null
+++ b/branch.h
@@ -0,0 +1,24 @@
+#ifndef BRANCH_H
+#define BRANCH_H
+
+/* Functions for acting on the information about branches. */
+
+/*
+ * Creates a new branch, where head is the branch currently checked
+ * out, name is the new branch name, start_name is the name of the
+ * existing branch that the new branch should start from, force
+ * enables overwriting an existing (non-head) branch, reflog creates a
+ * reflog for the branch, and track causes the new branch to be
+ * configured to merge the remote branch that start_name is a tracking
+ * branch for (if any).
+ */
+void create_branch(const char *head, const char *name, const char *start_name,
+ int force, int reflog, enum branch_track track);
+
+/*
+ * Remove information about the state of working on the current
+ * branch. (E.g., MERGE_HEAD)
+ */
+void remove_branch_state(void);
+
+#endif
diff --git a/builtin-apply.c b/builtin-apply.c
index 6a88ff018..a3f075df4 100644
--- a/builtin-apply.c
+++ b/builtin-apply.c
@@ -161,6 +161,84 @@ struct patch {
struct patch *next;
};
+/*
+ * A line in a file, len-bytes long (includes the terminating LF,
+ * except for an incomplete line at the end if the file ends with
+ * one), and its contents hashes to 'hash'.
+ */
+struct line {
+ size_t len;
+ unsigned hash : 24;
+ unsigned flag : 8;
+#define LINE_COMMON 1
+};
+
+/*
+ * This represents a "file", which is an array of "lines".
+ */
+struct image {
+ char *buf;
+ size_t len;
+ size_t nr;
+ size_t alloc;
+ struct line *line_allocated;
+ struct line *line;
+};
+
+static uint32_t hash_line(const char *cp, size_t len)
+{
+ size_t i;
+ uint32_t h;
+ for (i = 0, h = 0; i < len; i++) {
+ if (!isspace(cp[i])) {
+ h = h * 3 + (cp[i] & 0xff);
+ }
+ }
+ return h;
+}
+
+static void add_line_info(struct image *img, const char *bol, size_t len, unsigned flag)
+{
+ ALLOC_GROW(img->line_allocated, img->nr + 1, img->alloc);
+ img->line_allocated[img->nr].len = len;
+ img->line_allocated[img->nr].hash = hash_line(bol, len);
+ img->line_allocated[img->nr].flag = flag;
+ img->nr++;
+}
+
+static void prepare_image(struct image *image, char *buf, size_t len,
+ int prepare_linetable)
+{
+ const char *cp, *ep;
+
+ memset(image, 0, sizeof(*image));
+ image->buf = buf;
+ image->len = len;
+
+ if (!prepare_linetable)
+ return;
+
+ ep = image->buf + image->len;
+ cp = image->buf;
+ while (cp < ep) {
+ const char *next;
+ for (next = cp; next < ep && *next != '\n'; next++)
+ ;
+ if (next < ep)
+ next++;
+ add_line_info(image, cp, next - cp, 0);
+ cp = next;
+ }
+ image->line = image->line_allocated;
+}
+
+static void clear_image(struct image *image)
+{
+ free(image->buf);
+ image->buf = NULL;
+ image->len = 0;
+}
+
static void say_patch_name(FILE *output, const char *pre,
struct patch *patch, const char *post)
{
@@ -1437,227 +1515,338 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
}
}
-static int find_offset(const char *buf, unsigned long size,
- const char *fragment, unsigned long fragsize,
- int line, int *lines)
+static void update_pre_post_images(struct image *preimage,
+ struct image *postimage,
+ char *buf,
+ size_t len)
{
- int i;
- unsigned long start, backwards, forwards;
+ int i, ctx;
+ char *new, *old, *fixed;
+ struct image fixed_preimage;
- if (fragsize > size)
- return -1;
+ /*
+ * Update the preimage with whitespace fixes. Note that we
+ * are not losing preimage->buf -- apply_one_fragment() will
+ * free "oldlines".
+ */
+ prepare_image(&fixed_preimage, buf, len, 1);
+ assert(fixed_preimage.nr == preimage->nr);
+ for (i = 0; i < preimage->nr; i++)
+ fixed_preimage.line[i].flag = preimage->line[i].flag;
+ free(preimage->line_allocated);
+ *preimage = fixed_preimage;
- start = 0;
- if (line > 1) {
- unsigned long offset = 0;
- i = line-1;
- while (offset + fragsize <= size) {
- if (buf[offset++] == '\n') {
- start = offset;
- if (!--i)
- break;
- }
+ /*
+ * Adjust the common context lines in postimage, in place.
+ * This is possible because whitespace fixing does not make
+ * the string grow.
+ */
+ new = old = postimage->buf;
+ fixed = preimage->buf;
+ for (i = ctx = 0; i < postimage->nr; i++) {
+ size_t len = postimage->line[i].len;
+ if (!(postimage->line[i].flag & LINE_COMMON)) {
+ /* an added line -- no counterparts in preimage */
+ memmove(new, old, len);
+ old += len;
+ new += len;
+ continue;
}
+
+ /* a common context -- skip it in the original postimage */
+ old += len;
+
+ /* and find the corresponding one in the fixed preimage */
+ while (ctx < preimage->nr &&
+ !(preimage->line[ctx].flag & LINE_COMMON)) {
+ fixed += preimage->line[ctx].len;
+ ctx++;
+ }
+ if (preimage->nr <= ctx)
+ die("oops");
+
+ /* and copy it in, while fixing the line length */
+ len = preimage->line[ctx].len;
+ memcpy(new, fixed, len);
+ new += len;
+ fixed += len;
+ postimage->line[i].len = len;
+ ctx++;
}
- /* Exact line number? */
- if ((start + fragsize <= size) &&
- !memcmp(buf + start, fragment, fragsize))
- return start;
+ /* Fix the length of the whole thing */
+ postimage->len = new - postimage->buf;
+}
+
+static int match_fragment(struct image *img,
+ struct image *preimage,
+ struct image *postimage,
+ unsigned long try,
+ int try_lno,
+ unsigned ws_rule,
+ int match_beginning, int match_end)
+{
+ int i;
+ char *fixed_buf, *buf, *orig, *target;
+
+ if (preimage->nr + try_lno > img->nr)
+ return 0;
+
+ if (match_beginning && try_lno)
+ return 0;
+
+ if (match_end && preimage->nr + try_lno != img->nr)
+ return 0;
+
+ /* Quick hash check */
+ for (i = 0; i < preimage->nr; i++)
+ if (preimage->line[i].hash != img->line[try_lno + i].hash)
+ return 0;
+
+ /*
+ * Do we have an exact match? If we were told to match
+ * at the end, size must be exactly at try+fragsize,
+ * otherwise try+fragsize must be still within the preimage,
+ * and either case, the old piece should match the preimage
+ * exactly.
+ */
+ if ((match_end
+ ? (try + preimage->len == img->len)
+ : (try + preimage->len <= img->len)) &&
+ !memcmp(img->buf + try, preimage->buf, preimage->len))
+ return 1;
+
+ if (ws_error_action != correct_ws_error)
+ return 0;
+
+ /*
+ * The hunk does not apply byte-by-byte, but the hash says
+ * it might with whitespace fuzz.
+ */
+ fixed_buf = xmalloc(preimage->len + 1);
+ buf = fixed_buf;
+ orig = preimage->buf;
+ target = img->buf + try;
+ for (i = 0; i < preimage->nr; i++) {
+ size_t fixlen; /* length after fixing the preimage */
+ size_t oldlen = preimage->line[i].len;
+ size_t tgtlen = img->line[try_lno + i].len;
+ size_t tgtfixlen; /* length after fixing the target line */
+ char tgtfixbuf[1024], *tgtfix;
+ int match;
+
+ /* Try fixing the line in the preimage */
+ fixlen = ws_fix_copy(buf, orig, oldlen, ws_rule, NULL);
+
+ /* Try fixing the line in the target */
+ if (sizeof(tgtfixbuf) < tgtlen)
+ tgtfix = tgtfixbuf;
+ else
+ tgtfix = xmalloc(tgtlen);
+ tgtfixlen = ws_fix_copy(tgtfix, target, tgtlen, ws_rule, NULL);
+
+ /*
+ * If they match, either the preimage was based on
+ * a version before our tree fixed whitespace breakage,
+ * or we are lacking a whitespace-fix patch the tree
+ * the preimage was based on already had (i.e. target
+ * has whitespace breakage, the preimage doesn't).
+ * In either case, we are fixing the whitespace breakages
+ * so we might as well take the fix together with their
+ * real change.
+ */
+ match = (tgtfixlen == fixlen && !memcmp(tgtfix, buf, fixlen));
+
+ if (tgtfix != tgtfixbuf)
+ free(tgtfix);
+ if (!match)
+ goto unmatch_exit;
+
+ orig += oldlen;
+ buf += fixlen;
+ target += tgtlen;
+ }
+
+ /*
+ * Yes, the preimage is based on an older version that still
+ * has whitespace breakages unfixed, and fixing them makes the
+ * hunk match. Update the context lines in the postimage.
+ */
+ update_pre_post_images(preimage, postimage,
+ fixed_buf, buf - fixed_buf);
+ return 1;
+
+ unmatch_exit:
+ free(fixed_buf);
+ return 0;
+}
+
+static int find_pos(struct image *img,
+ struct image *preimage,
+ struct image *postimage,
+ int line,
+ unsigned ws_rule,
+ int match_beginning, int match_end)
+{
+ int i;
+ unsigned long backwards, forwards, try;
+ int backwards_lno, forwards_lno, try_lno;
+
+ if (preimage->nr > img->nr)
+ return -1;
+
+ /*
+ * If match_begining or match_end is specified, there is no
+ * point starting from a wrong line that will never match and
+ * wander around and wait for a match at the specified end.
+ */
+ if (match_beginning)
+ line = 0;
+ else if (match_end)
+ line = img->nr - preimage->nr;
+
+ if (line > img->nr)
+ line = img->nr;
+
+ try = 0;
+ for (i = 0; i < line; i++)
+ try += img->line[i].len;
/*
* There's probably some smart way to do this, but I'll leave
* that to the smart and beautiful people. I'm simple and stupid.
*/
- backwards = start;
- forwards = start;
+ backwards = try;
+ backwards_lno = line;
+ forwards = try;
+ forwards_lno = line;
+ try_lno = line;
+
for (i = 0; ; i++) {
- unsigned long try;
- int n;
+ if (match_fragment(img, preimage, postimage,
+ try, try_lno, ws_rule,
+ match_beginning, match_end))
+ return try_lno;
+
+ again:
+ if (backwards_lno == 0 && forwards_lno == img->nr)
+ break;
- /* "backward" */
if (i & 1) {
- if (!backwards) {
- if (forwards + fragsize > size)
- break;
- continue;
+ if (backwards_lno == 0) {
+ i++;
+ goto again;
}
- do {
- --backwards;
- } while (backwards && buf[backwards-1] != '\n');
+ backwards_lno--;
+ backwards -= img->line[backwards_lno].len;
try = backwards;
+ try_lno = backwards_lno;
} else {
- while (forwards + fragsize <= size) {
- if (buf[forwards++] == '\n')
- break;
+ if (forwards_lno == img->nr) {
+ i++;
+ goto again;
}
+ forwards += img->line[forwards_lno].len;
+ forwards_lno++;
try = forwards;
+ try_lno = forwards_lno;
}
- if (try + fragsize > size)
- continue;
- if (memcmp(buf + try, fragment, fragsize))
- continue;
- n = (i >> 1)+1;
- if (i & 1)
- n = -n;
- *lines = n;
- return try;
}
-
- /*
- * We should start searching forward and backward.
- */
return -1;
}
-static void remove_first_line(const char **rbuf, int *rsize)
+static void remove_first_line(struct image *img)
{
- const char *buf = *rbuf;
- int size = *rsize;
- unsigned long offset;
- offset = 0;
- while (offset <= size) {
- if (buf[offset++] == '\n')
- break;
- }
- *rsize = size - offset;
- *rbuf = buf + offset;
+ img->buf += img->line[0].len;
+ img->len -= img->line[0].len;
+ img->line++;
+ img->nr--;
}
-static void remove_last_line(const char **rbuf, int *rsize)
+static void remove_last_line(struct image *img)
{
- const char *buf = *rbuf;
- int size = *rsize;
- unsigned long offset;
- offset = size - 1;
- while (offset > 0) {
- if (buf[--offset] == '\n')
- break;
- }
- *rsize = offset + 1;
+ img->len -= img->line[--img->nr].len;
}
-static int apply_line(char *output, const char *patch, int plen,
- unsigned ws_rule)
+static void update_image(struct image *img,
+ int applied_pos,
+ struct image *preimage,
+ struct image *postimage)
{
/*
- * plen is number of bytes to be copied from patch,
- * starting at patch+1 (patch[0] is '+'). Typically
- * patch[plen] is '\n', unless this is the incomplete
- * last line.
+ * remove the copy of preimage at offset in img
+ * and replace it with postimage
*/
- int i;
- int add_nl_to_tail = 0;
- int fixed = 0;
- int last_tab_in_indent = 0;
- int last_space_in_indent = 0;
- int need_fix_leading_space = 0;
- char *buf;
-
- if ((ws_error_action != correct_ws_error) || !whitespace_error ||
- *patch != '+') {
- memcpy(output, patch + 1, plen);
- return plen;
- }
-
- /*
- * Strip trailing whitespace
- */
- if ((ws_rule & WS_TRAILING_SPACE) &&
- (1 < plen && isspace(patch[plen-1]))) {
- if (patch[plen] == '\n')
- add_nl_to_tail = 1;
- plen--;
- while (0 < plen && isspace(patch[plen]))
- plen--;
- fixed = 1;
- }
-
- /*
- * Check leading whitespaces (indent)
- */
- for (i = 1; i < plen; i++) {
- char ch = patch[i];
- if (ch == '\t') {
- last_tab_in_indent = i;
- if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
- 0 < last_space_in_indent)
- need_fix_leading_space = 1;
- } else if (ch == ' ') {
- last_space_in_indent = i;
- if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
- 8 <= i - last_tab_in_indent)
- need_fix_leading_space = 1;
- }
- else
- break;
- }
-
- buf = output;
- if (need_fix_leading_space) {
- int consecutive_spaces = 0;
- int last = last_tab_in_indent + 1;
-
- if (ws_rule & WS_INDENT_WITH_NON_TAB) {
- /* have "last" point at one past the indent */
- if (last_tab_in_indent < last_space_in_indent)
- last = last_space_in_indent + 1;
- else
- last = last_tab_in_indent + 1;
- }
+ int i, nr;
+ size_t remove_count, insert_count, applied_at = 0;
+ char *result;
+ for (i = 0; i < applied_pos; i++)
+ applied_at += img->line[i].len;
+
+ remove_count = 0;
+ for (i = 0; i < preimage->nr; i++)
+ remove_count += img->line[applied_pos + i].len;
+ insert_count = postimage->len;
+
+ /* Adjust the contents */
+ result = xmalloc(img->len + insert_count - remove_count + 1);
+ memcpy(result, img->buf, applied_at);
+ memcpy(result + applied_at, postimage->buf, postimage->len);
+ memcpy(result + applied_at + postimage->len,
+ img->buf + (applied_at + remove_count),
+ img->len - (applied_at + remove_count));
+ free(img->buf);
+ img->buf = result;
+ img->len += insert_count - remove_count;
+ result[img->len] = '\0';
+
+ /* Adjust the line table */
+ nr = img->nr + postimage->nr - preimage->nr;
+ if (preimage->nr < postimage->nr) {
/*
- * between patch[1..last], strip the funny spaces,
- * updating them to tab as needed.
+ * NOTE: this knows that we never call remove_first_line()
+ * on anything other than pre/post image.
*/
- for (i = 1; i < last; i++, plen--) {
- char ch = patch[i];
- if (ch != ' ') {
- consecutive_spaces = 0;
- *output++ = ch;
- } else {
- consecutive_spaces++;
- if (consecutive_spaces == 8) {
- *output++ = '\t';
- consecutive_spaces = 0;
- }
- }
- }
- while (0 < consecutive_spaces--)
- *output++ = ' ';
- fixed = 1;
- i = last;
+ img->line = xrealloc(img->line, nr * sizeof(*img->line));
+ img->line_allocated = img->line;
}
- else
- i = 1;
-
- memcpy(output, patch + i, plen);
- if (add_nl_to_tail)
- output[plen++] = '\n';
- if (fixed)
- applied_after_fixing_ws++;
- return output + plen - buf;
+ if (preimage->nr != postimage->nr)
+ memmove(img->line + applied_pos + postimage->nr,
+ img->line + applied_pos + preimage->nr,
+ (img->nr - (applied_pos + preimage->nr)) *
+ sizeof(*img->line));
+ memcpy(img->line + applied_pos,
+ postimage->line,
+ postimage->nr * sizeof(*img->line));
+ img->nr = nr;
}
-static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
+static int apply_one_fragment(struct image *img, struct fragment *frag,
int inaccurate_eof, unsigned ws_rule)
{
int match_beginning, match_end;
const char *patch = frag->patch;
- int offset, size = frag->size;
- char *old = xmalloc(size);
- char *new = xmalloc(size);
- const char *oldlines, *newlines;
- int oldsize = 0, newsize = 0;
+ int size = frag->size;
+ char *old, *new, *oldlines, *newlines;
int new_blank_lines_at_end = 0;
unsigned long leading, trailing;
- int pos, lines;
+ int pos, applied_pos;
+ struct image preimage;
+ struct image postimage;
+
+ memset(&preimage, 0, sizeof(preimage));
+ memset(&postimage, 0, sizeof(postimage));
+ oldlines = xmalloc(size);
+ newlines = xmalloc(size);
+ old = oldlines;
+ new = newlines;
while (size > 0) {
char first;
int len = linelen(patch, size);
- int plen;
+ int plen, added;
int added_blank_line = 0;
if (!len)
@@ -1670,7 +1859,7 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
* followed by "\ No newline", then we also remove the
* last one (which is the newline, of course).
*/
- plen = len-1;
+ plen = len - 1;
if (len < size && patch[len] == '\\')
plen--;
first = *patch;
@@ -1687,25 +1876,40 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
if (plen < 0)
/* ... followed by '\No newline'; nothing */
break;
- old[oldsize++] = '\n';
- new[newsize++] = '\n';
+ *old++ = '\n';
+ *new++ = '\n';
+ add_line_info(&preimage, "\n", 1, LINE_COMMON);
+ add_line_info(&postimage, "\n", 1, LINE_COMMON);
break;
case ' ':
case '-':
- memcpy(old + oldsize, patch + 1, plen);
- oldsize += plen;
+ memcpy(old, patch + 1, plen);
+ add_line_info(&preimage, old, plen,
+ (first == ' ' ? LINE_COMMON : 0));
+ old += plen;
if (first == '-')
break;
/* Fall-through for ' ' */
case '+':
- if (first != '+' || !no_add) {
- int added = apply_line(new + newsize, patch,
- plen, ws_rule);
- newsize += added;
- if (first == '+' &&
- added == 1 && new[newsize-1] == '\n')
- added_blank_line = 1;
+ /* --no-add does not add new lines */
+ if (first == '+' && no_add)
+ break;
+
+ if (first != '+' ||
+ !whitespace_error ||
+ ws_error_action != correct_ws_error) {
+ memcpy(new, patch + 1, plen);
+ added = plen;
}
+ else {
+ added = ws_fix_copy(new, patch + 1, plen, ws_rule, &applied_after_fixing_ws);
+ }
+ add_line_info(&postimage, new, added,
+ (first == '+' ? 0 : LINE_COMMON));
+ new += added;
+ if (first == '+' &&
+ added == 1 && new[-1] == '\n')
+ added_blank_line = 1;
break;
case '@': case '\\':
/* Ignore it, we already handled it */
@@ -1722,16 +1926,13 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
patch += len;
size -= len;
}
-
if (inaccurate_eof &&
- oldsize > 0 && old[oldsize - 1] == '\n' &&
- newsize > 0 && new[newsize - 1] == '\n') {
- oldsize--;
- newsize--;
+ old > oldlines && old[-1] == '\n' &&
+ new > newlines && new[-1] == '\n') {
+ old--;
+ new--;
}
- oldlines = old;
- newlines = new;
leading = frag->leading;
trailing = frag->trailing;
@@ -1752,33 +1953,21 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
match_end = !trailing;
}
- lines = 0;
- pos = frag->newpos;
+ pos = frag->newpos ? (frag->newpos - 1) : 0;
+ preimage.buf = oldlines;
+ preimage.len = old - oldlines;
+ postimage.buf = newlines;
+ postimage.len = new - newlines;
+ preimage.line = preimage.line_allocated;
+ postimage.line = postimage.line_allocated;
+
for (;;) {
- offset = find_offset(buf->buf, buf->len,
- oldlines, oldsize, pos, &lines);
- if (match_end && offset + oldsize != buf->len)
- offset = -1;
- if (match_beginning && offset)
- offset = -1;
- if (offset >= 0) {
- if (ws_error_action == correct_ws_error &&
- (buf->len - oldsize - offset == 0)) /* end of file? */
- newsize -= new_blank_lines_at_end;
-
- /* Warn if it was necessary to reduce the number
- * of context lines.
- */
- if ((leading != frag->leading) ||
- (trailing != frag->trailing))
- fprintf(stderr, "Context reduced to (%ld/%ld)"
- " to apply fragment at %d\n",
- leading, trailing, pos + lines);
-
- strbuf_splice(buf, offset, oldsize, newlines, newsize);
- offset = 0;
+
+ applied_pos = find_pos(img, &preimage, &postimage, pos,
+ ws_rule, match_beginning, match_end);
+
+ if (applied_pos >= 0)
break;
- }
/* Am I at my context limits? */
if ((leading <= p_context) && (trailing <= p_context))
@@ -1787,33 +1976,64 @@ static int apply_one_fragment(struct strbuf *buf, struct fragment *frag,
match_beginning = match_end = 0;
continue;
}
+
/*
* Reduce the number of context lines; reduce both
* leading and trailing if they are equal otherwise
* just reduce the larger context.
*/
if (leading >= trailing) {
- remove_first_line(&oldlines, &oldsize);
- remove_first_line(&newlines, &newsize);
+ remove_first_line(&preimage);
+ remove_first_line(&postimage);
pos--;
leading--;
}
if (trailing > leading) {
- remove_last_line(&oldlines, &oldsize);
- remove_last_line(&newlines, &newsize);
+ remove_last_line(&preimage);
+ remove_last_line(&postimage);
trailing--;
}
}
- if (offset && apply_verbosely)
- error("while searching for:\n%.*s", oldsize, oldlines);
+ if (applied_pos >= 0) {
+ if (ws_error_action == correct_ws_error &&
+ new_blank_lines_at_end &&
+ postimage.nr + applied_pos == img->nr) {
+ /*
+ * If the patch application adds blank lines
+ * at the end, and if the patch applies at the
+ * end of the image, remove those added blank
+ * lines.
+ */
+ while (new_blank_lines_at_end--)
+ remove_last_line(&postimage);
+ }
- free(old);
- free(new);
- return offset;
+ /*
+ * Warn if it was necessary to reduce the number
+ * of context lines.
+ */
+ if ((leading != frag->leading) ||
+ (trailing != frag->trailing))
+ fprintf(stderr, "Context reduced to (%ld/%ld)"
+ " to apply fragment at %d\n",
+ leading, trailing, applied_pos+1);
+ update_image(img, applied_pos, &preimage, &postimage);
+ } else {
+ if (apply_verbosely)
+ error("while searching for:\n%.*s",
+ (int)(old - oldlines), oldlines);
+ }
+
+ free(oldlines);
+ free(newlines);
+ free(preimage.line_allocated);
+ free(postimage.line_allocated);
+
+ return (applied_pos < 0);
}
-static int apply_binary_fragment(struct strbuf *buf, struct patch *patch)
+static int apply_binary_fragment(struct image *img, struct patch *patch)
{
struct fragment *fragment = patch->fragments;
unsigned long len;
@@ -1830,22 +2050,26 @@ static int apply_binary_fragment(struct strbuf *buf, struct patch *patch)
}
switch (fragment->binary_patch_method) {
case BINARY_DELTA_DEFLATED:
- dst = patch_delta(buf->buf, buf->len, fragment->patch,
+ dst = patch_delta(img->buf, img->len, fragment->patch,
fragment->size, &len);
if (!dst)
return -1;
- /* XXX patch_delta NUL-terminates */
- strbuf_attach(buf, dst, len, len + 1);
+ clear_image(img);
+ img->buf = dst;
+ img->len = len;
return 0;
case BINARY_LITERAL_DEFLATED:
- strbuf_reset(buf);
- strbuf_add(buf, fragment->patch, fragment->size);
+ clear_image(img);
+ img->len = fragment->size;
+ img->buf = xmalloc(img->len+1);
+ memcpy(img->buf, fragment->patch, img->len);
+ img->buf[img->len] = '\0';
return 0;
}
return -1;
}
-static int apply_binary(struct strbuf *buf, struct patch *patch)
+static int apply_binary(struct image *img, struct patch *patch)
{
const char *name = patch->old_name ? patch->old_name : patch->new_name;
unsigned char sha1[20];
@@ -1866,7 +2090,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
* See if the old one matches what the patch
* applies to.
*/
- hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
+ hash_sha1_file(img->buf, img->len, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->old_sha1_prefix))
return error("the patch applies to '%s' (%s), "
"which does not match the "
@@ -1875,14 +2099,14 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
}
else {
/* Otherwise, the old one must be empty. */
- if (buf->len)
+ if (img->len)
return error("the patch applies to an empty "
"'%s' but it is not empty", name);
}
get_sha1_hex(patch->new_sha1_prefix, sha1);
if (is_null_sha1(sha1)) {
- strbuf_release(buf);
+ clear_image(img);
return 0; /* deletion patch */
}
@@ -1897,20 +2121,21 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
return error("the necessary postimage %s for "
"'%s' cannot be read",
patch->new_sha1_prefix, name);
- /* XXX read_sha1_file NUL-terminates */
- strbuf_attach(buf, result, size, size + 1);
+ clear_image(img);
+ img->buf = result;
+ img->len = size;
} else {
/*
* We have verified buf matches the preimage;
* apply the patch data to it, which is stored
* in the patch->fragments->{patch,size}.
*/
- if (apply_binary_fragment(buf, patch))
+ if (apply_binary_fragment(img, patch))
return error("binary patch does not apply to '%s'",
name);
/* verify that the result matches */
- hash_sha1_file(buf->buf, buf->len, blob_type, sha1);
+ hash_sha1_file(img->buf, img->len, blob_type, sha1);
if (strcmp(sha1_to_hex(sha1), patch->new_sha1_prefix))
return error("binary patch to '%s' creates incorrect result (expecting %s, got %s)",
name, patch->new_sha1_prefix, sha1_to_hex(sha1));
@@ -1919,7 +2144,7 @@ static int apply_binary(struct strbuf *buf, struct patch *patch)
return 0;
}
-static int apply_fragments(struct strbuf *buf, struct patch *patch)
+static int apply_fragments(struct image *img, struct patch *patch)
{
struct fragment *frag = patch->fragments;
const char *name = patch->old_name ? patch->old_name : patch->new_name;
@@ -1927,10 +2152,10 @@ static int apply_fragments(struct strbuf *buf, struct patch *patch)
unsigned inaccurate_eof = patch->inaccurate_eof;
if (patch->is_binary)
- return apply_binary(buf, patch);
+ return apply_binary(img, patch);
while (frag) {
- if (apply_one_fragment(buf, frag, inaccurate_eof, ws_rule)) {
+ if (apply_one_fragment(img, frag, inaccurate_eof, ws_rule)) {
error("patch failed: %s:%ld", name, frag->oldpos);
if (!apply_with_reject)
return -1;
@@ -1966,6 +2191,9 @@ static int read_file_or_gitlink(struct cache_entry *ce, struct strbuf *buf)
static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *ce)
{
struct strbuf buf;
+ struct image image;
+ size_t len;
+ char *img;
strbuf_init(&buf, 0);
if (cached) {
@@ -1988,9 +2216,14 @@ static int apply_data(struct patch *patch, struct stat *st, struct cache_entry *
}
}
- if (apply_fragments(&buf, patch) < 0)
+ img = strbuf_detach(&buf, &len);
+ prepare_image(&image, img, len, !patch->is_binary);
+
+ if (apply_fragments(&image, patch) < 0)
return -1; /* note with --reject this succeeds. */
- patch->result = strbuf_detach(&buf, &patch->resultsize);
+ patch->result = image.buf;
+ patch->resultsize = image.len;
+ free(image.line_allocated);
if (0 < patch->is_delete && patch->resultsize)
return error("removal patch leaves file contents");
diff --git a/builtin-branch.c b/builtin-branch.c
index 79177007e..5bc4526f6 100644
--- a/builtin-branch.c
+++ b/builtin-branch.c
@@ -12,6 +12,7 @@
#include "builtin.h"
#include "remote.h"
#include "parse-options.h"
+#include "branch.h"
static const char * const builtin_branch_usage[] = {
"git-branch [options] [-r | -a]",
@@ -29,8 +30,6 @@ static const char * const builtin_branch_usage[] = {
static const char *head;
static unsigned char head_sha1[20];
-static int branch_track = 1;
-
static int branch_use_color = -1;
static char branch_colors[][COLOR_MAXLEN] = {
"\033[m", /* reset */
@@ -75,10 +74,6 @@ static int git_branch_config(const char *var, const char *value)
color_parse(value, var, branch_colors[slot]);
return 0;
}
- if (!strcmp(var, "branch.autosetupmerge")) {
- branch_track = git_config_bool(var, value);
- return 0;
- }
return git_color_default_config(var, value);
}
@@ -357,140 +352,6 @@ static void print_ref_list(int kinds, int detached, int verbose, int abbrev, str
free_ref_list(&ref_list);
}
-struct tracking {
- struct refspec spec;
- char *src;
- const char *remote;
- int matches;
-};
-
-static int find_tracked_branch(struct remote *remote, void *priv)
-{
- struct tracking *tracking = priv;
-
- if (!remote_find_tracking(remote, &tracking->spec)) {
- if (++tracking->matches == 1) {
- tracking->src = tracking->spec.src;
- tracking->remote = remote->name;
- } else {
- free(tracking->spec.src);
- if (tracking->src) {
- free(tracking->src);
- tracking->src = NULL;
- }
- }
- tracking->spec.src = NULL;
- }
-
- return 0;
-}
-
-
-/*
- * This is called when new_ref is branched off of orig_ref, and tries
- * to infer the settings for branch.<new_ref>.{remote,merge} from the
- * config.
- */
-static int setup_tracking(const char *new_ref, const char *orig_ref)
-{
- char key[1024];
- struct tracking tracking;
-
- if (strlen(new_ref) > 1024 - 7 - 7 - 1)
- return error("Tracking not set up: name too long: %s",
- new_ref);
-
- memset(&tracking, 0, sizeof(tracking));
- tracking.spec.dst = (char *)orig_ref;
- if (for_each_remote(find_tracked_branch, &tracking) ||
- !tracking.matches)
- return 1;
-
- if (tracking.matches > 1)
- return error("Not tracking: ambiguous information for ref %s",
- orig_ref);
-
- if (tracking.matches == 1) {
- sprintf(key, "branch.%s.remote", new_ref);
- git_config_set(key, tracking.remote ? tracking.remote : ".");
- sprintf(key, "branch.%s.merge", new_ref);
- git_config_set(key, tracking.src);
- free(tracking.src);
- printf("Branch %s set up to track remote branch %s.\n",
- new_ref, orig_ref);
- }
-
- return 0;
-}
-
-static void create_branch(const char *name, const char *start_name,
- int force, int reflog, int track)
-{
- struct ref_lock *lock;
- struct commit *commit;
- unsigned char sha1[20];
- char *real_ref, ref[PATH_MAX], msg[PATH_MAX + 20];
- int forcing = 0;
-
- snprintf(ref, sizeof ref, "refs/heads/%s", name);
- if (check_ref_format(ref))
- die("'%s' is not a valid branch name.", name);
-
- if (resolve_ref(ref, sha1, 1, NULL)) {
- if (!force)
- die("A branch named '%s' already exists.", name);
- else if (!is_bare_repository() && !strcmp(head, name))
- die("Cannot force update the current branch.");
- forcing = 1;
- }
-
- real_ref = NULL;
- if (get_sha1(start_name, sha1))
- die("Not a valid object name: '%s'.", start_name);
-
- switch (dwim_ref(start_name, strlen(start_name), sha1, &real_ref)) {
- case 0:
- /* Not branching from any existing branch */
- real_ref = NULL;
- break;
- case 1:
- /* Unique completion -- good */
- break;
- default:
- die("Ambiguous object name: '%s'.", start_name);
- break;
- }
-
- if ((commit = lookup_commit_reference(sha1)) == NULL)
- die("Not a valid branch point: '%s'.", start_name);
- hashcpy(sha1, commit->object.sha1);
-
- lock = lock_any_ref_for_update(ref, NULL, 0);
- if (!lock)
- die("Failed to lock ref for update: %s.", strerror(errno));
-
- if (reflog)
- log_all_ref_updates = 1;
-
- if (forcing)
- snprintf(msg, sizeof msg, "branch: Reset from %s",
- start_name);
- else
- snprintf(msg, sizeof msg, "branch: Created from %s",
- start_name);
-
- /* When branching off a remote branch, set up so that git-pull
- automatically merges from there. So far, this is only done for
- remotes registered via .git/config. */
- if (real_ref && track)
- setup_tracking(name, real_ref);
-
- if (write_ref_sha1(lock, sha1, msg) < 0)
- die("Failed to write ref: %s.", strerror(errno));
-
- free(real_ref);
-}
-
static void rename_branch(const char *oldname, const char *newname, int force)
{
char oldref[PATH_MAX], newref[PATH_MAX], logmsg[PATH_MAX*2 + 100];
@@ -551,14 +412,16 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
{
int delete = 0, rename = 0, force_create = 0;
int verbose = 0, abbrev = DEFAULT_ABBREV, detached = 0;
- int reflog = 0, track;
+ int reflog = 0;
+ enum branch_track track;
int kinds = REF_LOCAL_BRANCH;
struct commit_list *with_commit = NULL;
struct option options[] = {
OPT_GROUP("Generic options"),
OPT__VERBOSE(&verbose),
- OPT_BOOLEAN( 0 , "track", &track, "set up tracking mode (see git-pull(1))"),
+ OPT_SET_INT( 0 , "track", &track, "set up tracking mode (see git-pull(1))",
+ BRANCH_TRACK_EXPLICIT),
OPT_BOOLEAN( 0 , "color", &branch_use_color, "use colored output"),
OPT_SET_INT('r', NULL, &kinds, "act on remote-tracking branches",
REF_REMOTE_BRANCH),
@@ -589,7 +452,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
if (branch_use_color == -1)
branch_use_color = git_use_color_default;
- track = branch_track;
+ track = git_branch_track;
argc = parse_options(argc, argv, options, builtin_branch_usage, 0);
if (!!delete + !!rename + !!force_create > 1)
usage_with_options(builtin_branch_usage, options);
@@ -615,7 +478,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
else if (rename && (argc == 2))
rename_branch(argv[0], argv[1], rename > 1);
else if (argc <= 2)
- create_branch(argv[0], (argc == 2) ? argv[1] : head,
+ create_branch(head, argv[0], (argc == 2) ? argv[1] : head,
force_create, reflog, track);
else
usage_with_options(builtin_branch_usage, options);
diff --git a/builtin-checkout.c b/builtin-checkout.c
new file mode 100644
index 000000000..4a4bb8b77
--- /dev/null
+++ b/builtin-checkout.c
@@ -0,0 +1,569 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "refs.h"
+#include "commit.h"
+#include "tree.h"
+#include "tree-walk.h"
+#include "unpack-trees.h"
+#include "dir.h"
+#include "run-command.h"
+#include "merge-recursive.h"
+#include "branch.h"
+#include "diff.h"
+#include "revision.h"
+#include "remote.h"
+
+static const char * const checkout_usage[] = {
+ "git checkout [options] <branch>",
+ "git checkout [options] [<branch>] -- <file>...",
+ NULL,
+};
+
+static int post_checkout_hook(struct commit *old, struct commit *new,
+ int changed)
+{
+ struct child_process proc;
+ const char *name = git_path("hooks/post-checkout");
+ const char *argv[5];
+
+ if (access(name, X_OK) < 0)
+ return 0;
+
+ memset(&proc, 0, sizeof(proc));
+ argv[0] = name;
+ argv[1] = xstrdup(sha1_to_hex(old->object.sha1));
+ argv[2] = xstrdup(sha1_to_hex(new->object.sha1));
+ argv[3] = changed ? "1" : "0";
+ argv[4] = NULL;
+ proc.argv = argv;
+ proc.no_stdin = 1;
+ proc.stdout_to_stderr = 1;
+ return run_command(&proc);
+}
+
+static int update_some(const unsigned char *sha1, const char *base, int baselen,
+ const char *pathname, unsigned mode, int stage)
+{
+ int len;
+ struct cache_entry *ce;
+
+ if (S_ISGITLINK(mode))
+ return 0;
+
+ if (S_ISDIR(mode))
+ return READ_TREE_RECURSIVE;
+
+ len = baselen + strlen(pathname);
+ ce = xcalloc(1, cache_entry_size(len));
+ hashcpy(ce->sha1, sha1);
+ memcpy(ce->name, base, baselen);
+ memcpy(ce->name + baselen, pathname, len - baselen);
+ ce->ce_flags = create_ce_flags(len, 0);
+ ce->ce_mode = create_ce_mode(mode);
+ add_cache_entry(ce, ADD_CACHE_OK_TO_ADD | ADD_CACHE_OK_TO_REPLACE);
+ return 0;
+}
+
+static int read_tree_some(struct tree *tree, const char **pathspec)
+{
+ int newfd;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+ newfd = hold_locked_index(lock_file, 1);
+ read_cache();
+
+ read_tree_recursive(tree, "", 0, 0, pathspec, update_some);
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ /* update the index with the given tree's info
+ * for all args, expanding wildcards, and exit
+ * with any non-zero return code.
+ */
+ return 0;
+}
+
+static int checkout_paths(const char **pathspec)
+{
+ int pos;
+ struct checkout state;
+ static char *ps_matched;
+ unsigned char rev[20];
+ int flag;
+ struct commit *head;
+
+ for (pos = 0; pathspec[pos]; pos++)
+ ;
+ ps_matched = xcalloc(1, pos);
+
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ pathspec_match(pathspec, ps_matched, ce->name, 0);
+ }
+
+ if (report_path_error(ps_matched, pathspec, 0))
+ return 1;
+
+ memset(&state, 0, sizeof(state));
+ state.force = 1;
+ state.refresh_cache = 1;
+ for (pos = 0; pos < active_nr; pos++) {
+ struct cache_entry *ce = active_cache[pos];
+ if (pathspec_match(pathspec, NULL, ce->name, 0)) {
+ checkout_entry(ce, &state, NULL);
+ }
+ }
+
+ resolve_ref("HEAD", rev, 0, &flag);
+ head = lookup_commit_reference_gently(rev, 1);
+
+ return post_checkout_hook(head, head, 0);
+}
+
+static void show_local_changes(struct object *head)
+{
+ struct rev_info rev;
+ /* I think we want full paths, even if we're in a subdirectory. */
+ init_revisions(&rev, NULL);
+ rev.abbrev = 0;
+ rev.diffopt.output_format |= DIFF_FORMAT_NAME_STATUS;
+ add_pending_object(&rev, head, NULL);
+ run_diff_index(&rev, 0);
+}
+
+static void describe_detached_head(char *msg, struct commit *commit)
+{
+ struct strbuf sb;
+ strbuf_init(&sb, 0);
+ parse_commit(commit);
+ pretty_print_commit(CMIT_FMT_ONELINE, commit, &sb, 0, "", "", 0, 0);
+ fprintf(stderr, "%s %s... %s\n", msg,
+ find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV), sb.buf);
+ strbuf_release(&sb);
+}
+
+static int reset_to_new(struct tree *tree, int quiet)
+{
+ struct unpack_trees_options opts;
+ struct tree_desc tree_desc;
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = -1;
+ opts.update = 1;
+ opts.reset = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = !quiet;
+ parse_tree(tree);
+ init_tree_desc(&tree_desc, tree->buffer, tree->size);
+ if (unpack_trees(1, &tree_desc, &opts))
+ return 128;
+ return 0;
+}
+
+static void reset_clean_to_new(struct tree *tree, int quiet)
+{
+ struct unpack_trees_options opts;
+ struct tree_desc tree_desc;
+ memset(&opts, 0, sizeof(opts));
+ opts.head_idx = -1;
+ opts.skip_unmerged = 1;
+ opts.reset = 1;
+ opts.merge = 1;
+ opts.fn = oneway_merge;
+ opts.verbose_update = !quiet;
+ parse_tree(tree);
+ init_tree_desc(&tree_desc, tree->buffer, tree->size);
+ if (unpack_trees(1, &tree_desc, &opts))
+ exit(128);
+}
+
+struct checkout_opts {
+ int quiet;
+ int merge;
+ int force;
+
+ char *new_branch;
+ int new_branch_log;
+ enum branch_track track;
+};
+
+struct branch_info {
+ const char *name; /* The short name used */
+ const char *path; /* The full name of a real branch */
+ struct commit *commit; /* The named commit */
+};
+
+static void setup_branch_path(struct branch_info *branch)
+{
+ struct strbuf buf;
+ strbuf_init(&buf, 0);
+ strbuf_addstr(&buf, "refs/heads/");
+ strbuf_addstr(&buf, branch->name);
+ branch->path = strbuf_detach(&buf, NULL);
+}
+
+static int merge_working_tree(struct checkout_opts *opts,
+ struct branch_info *old, struct branch_info *new)
+{
+ int ret;
+ struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
+ int newfd = hold_locked_index(lock_file, 1);
+ read_cache();
+
+ if (opts->force) {
+ ret = reset_to_new(new->commit->tree, opts->quiet);
+ if (ret)
+ return ret;
+ } else {
+ struct tree_desc trees[2];
+ struct tree *tree;
+ struct unpack_trees_options topts;
+ memset(&topts, 0, sizeof(topts));
+ topts.head_idx = -1;
+
+ refresh_cache(REFRESH_QUIET);
+
+ if (unmerged_cache()) {
+ error("you need to resolve your current index first");
+ return 1;
+ }
+
+ /* 2-way merge to the new branch */
+ topts.update = 1;
+ topts.merge = 1;
+ topts.gently = opts->merge;
+ topts.verbose_update = !opts->quiet;
+ topts.fn = twoway_merge;
+ topts.dir = xcalloc(1, sizeof(*topts.dir));
+ topts.dir->show_ignored = 1;
+ topts.dir->exclude_per_dir = ".gitignore";
+ tree = parse_tree_indirect(old->commit->object.sha1);
+ init_tree_desc(&trees[0], tree->buffer, tree->size);
+ tree = parse_tree_indirect(new->commit->object.sha1);
+ init_tree_desc(&trees[1], tree->buffer, tree->size);
+
+ if (unpack_trees(2, trees, &topts)) {
+ /*
+ * Unpack couldn't do a trivial merge; either
+ * give up or do a real merge, depending on
+ * whether the merge flag was used.
+ */
+ struct tree *result;
+ struct tree *work;
+ if (!opts->merge)
+ return 1;
+ parse_commit(old->commit);
+
+ /* Do more real merge */
+
+ /*
+ * We update the index fully, then write the
+ * tree from the index, then merge the new
+ * branch with the current tree, with the old
+ * branch as the base. Then we reset the index
+ * (but not the working tree) to the new
+ * branch, leaving the working tree as the
+ * merged version, but skipping unmerged
+ * entries in the index.
+ */
+
+ add_files_to_cache(0, NULL, NULL);
+ work = write_tree_from_memory();
+
+ ret = reset_to_new(new->commit->tree, opts->quiet);
+ if (ret)
+ return ret;
+ merge_trees(new->commit->tree, work, old->commit->tree,
+ new->name, "local", &result);
+ reset_clean_to_new(new->commit->tree, opts->quiet);
+ }
+ }
+
+ if (write_cache(newfd, active_cache, active_nr) ||
+ commit_locked_index(lock_file))
+ die("unable to write new index file");
+
+ if (!opts->force)
+ show_local_changes(&new->commit->object);
+
+ return 0;
+}
+
+static void report_tracking(struct branch_info *new, struct checkout_opts *opts)
+{
+ /*
+ * We have switched to a new branch; is it building on
+ * top of another branch, and if so does that other branch
+ * have changes we do not have yet?
+ */
+ char *base;
+ unsigned char sha1[20];
+ struct commit *ours, *theirs;
+ char symmetric[84];
+ struct rev_info revs;
+ const char *rev_argv[10];
+ int rev_argc;
+ int num_ours, num_theirs;
+ const char *remote_msg;
+ struct branch *branch = branch_get(new->name);
+
+ /*
+ * Nothing to report unless we are marked to build on top of
+ * somebody else.
+ */
+ if (!branch || !branch->merge || !branch->merge[0] || !branch->merge[0]->dst)
+ return;
+
+ /*
+ * If what we used to build on no longer exists, there is
+ * nothing to report.
+ */
+ base = branch->merge[0]->dst;
+ if (!resolve_ref(base, sha1, 1, NULL))
+ return;
+
+ theirs = lookup_commit(sha1);
+ ours = new->commit;
+ if (!hashcmp(sha1, ours->object.sha1))
+ return; /* we are the same */
+
+ /* Run "rev-list --left-right ours...theirs" internally... */
+ rev_argc = 0;
+ rev_argv[rev_argc++] = NULL;
+ rev_argv[rev_argc++] = "--left-right";
+ rev_argv[rev_argc++] = symmetric;
+ rev_argv[rev_argc++] = "--";
+ rev_argv[rev_argc] = NULL;
+
+ strcpy(symmetric, sha1_to_hex(ours->object.sha1));
+ strcpy(symmetric + 40, "...");
+ strcpy(symmetric + 43, sha1_to_hex(theirs->object.sha1));
+
+ init_revisions(&revs, NULL);
+ setup_revisions(rev_argc, rev_argv, &revs, NULL);
+ prepare_revision_walk(&revs);
+
+ /* ... and count the commits on each side. */
+ num_ours = 0;
+ num_theirs = 0;
+ while (1) {
+ struct commit *c = get_revision(&revs);
+ if (!c)
+ break;
+ if (c->object.flags & SYMMETRIC_LEFT)
+ num_ours++;
+ else
+ num_theirs++;
+ }
+
+ if (!prefixcmp(base, "refs/remotes/")) {
+ remote_msg = " remote";
+ base += strlen("refs/remotes/");
+ } else {
+ remote_msg = "";
+ }
+
+ if (!num_theirs)
+ printf("Your branch is ahead of the tracked%s branch '%s' "
+ "by %d commit%s.\n",
+ remote_msg, base,
+ num_ours, (num_ours == 1) ? "" : "s");
+ else if (!num_ours)
+ printf("Your branch is behind the tracked%s branch '%s' "
+ "by %d commit%s,\n"
+ "and can be fast-forwarded.\n",
+ remote_msg, base,
+ num_theirs, (num_theirs == 1) ? "" : "s");
+ else
+ printf("Your branch and the tracked%s branch '%s' "
+ "have diverged,\nand respectively "
+ "have %d and %d different commit(s) each.\n",
+ remote_msg, base,
+ num_ours, num_theirs);
+}
+
+static void update_refs_for_switch(struct checkout_opts *opts,
+ struct branch_info *old,
+ struct branch_info *new)
+{
+ struct strbuf msg;
+ const char *old_desc;
+ if (opts->new_branch) {
+ create_branch(old->name, opts->new_branch, new->name, 0,
+ opts->new_branch_log, opts->track);
+ new->name = opts->new_branch;
+ setup_branch_path(new);
+ }
+
+ strbuf_init(&msg, 0);
+ old_desc = old->name;
+ if (!old_desc)
+ old_desc = sha1_to_hex(old->commit->object.sha1);
+ strbuf_addf(&msg, "checkout: moving from %s to %s",
+ old_desc, new->name);
+
+ if (new->path) {
+ create_symref("HEAD", new->path, msg.buf);
+ if (!opts->quiet) {
+ if (old->path && !strcmp(new->path, old->path))
+ fprintf(stderr, "Already on \"%s\"\n",
+ new->name);
+ else
+ fprintf(stderr, "Switched to%s branch \"%s\"\n",
+ opts->new_branch ? " a new" : "",
+ new->name);
+ }
+ } else if (strcmp(new->name, "HEAD")) {
+ update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
+ 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);
+ describe_detached_head("HEAD is now at", new->commit);
+ }
+ }
+ remove_branch_state();
+ strbuf_release(&msg);
+ if (!opts->quiet && (new->path || !strcmp(new->name, "HEAD")))
+ report_tracking(new, opts);
+}
+
+static int switch_branches(struct checkout_opts *opts, struct branch_info *new)
+{
+ int ret = 0;
+ struct branch_info old;
+ unsigned char rev[20];
+ int flag;
+ memset(&old, 0, sizeof(old));
+ old.path = resolve_ref("HEAD", rev, 0, &flag);
+ old.commit = lookup_commit_reference_gently(rev, 1);
+ if (!(flag & REF_ISSYMREF))
+ old.path = NULL;
+
+ if (old.path && !prefixcmp(old.path, "refs/heads/"))
+ old.name = old.path + strlen("refs/heads/");
+
+ if (!new->name) {
+ new->name = "HEAD";
+ new->commit = old.commit;
+ if (!new->commit)
+ die("You are on a branch yet to be born");
+ parse_commit(new->commit);
+ }
+
+ /*
+ * If the new thing isn't a branch and isn't HEAD and we're
+ * not starting a new branch, and we want messages, and we
+ * weren't on a branch, and we're moving to a new commit,
+ * describe the old commit.
+ */
+ if (!new->path && strcmp(new->name, "HEAD") && !opts->new_branch &&
+ !opts->quiet && !old.path && new->commit != old.commit)
+ describe_detached_head("Previous HEAD position was", old.commit);
+
+ if (!old.commit) {
+ if (!opts->quiet) {
+ fprintf(stderr, "warning: You appear to be on a branch yet to be born.\n");
+ fprintf(stderr, "warning: Forcing checkout of %s.\n", new->name);
+ }
+ opts->force = 1;
+ }
+
+ ret = merge_working_tree(opts, &old, new);
+ if (ret)
+ return ret;
+
+ update_refs_for_switch(opts, &old, new);
+
+ return post_checkout_hook(old.commit, new->commit, 1);
+}
+
+static int git_checkout_config(const char *var, const char *value)
+{
+ return git_default_config(var, value);
+}
+
+int cmd_checkout(int argc, const char **argv, const char *prefix)
+{
+ struct checkout_opts opts;
+ unsigned char rev[20];
+ const char *arg;
+ struct branch_info new;
+ struct tree *source_tree = NULL;
+ struct option options[] = {
+ OPT__QUIET(&opts.quiet),
+ OPT_STRING('b', NULL, &opts.new_branch, "new branch", "branch"),
+ OPT_BOOLEAN('l', NULL, &opts.new_branch_log, "log for new branch"),
+ OPT_SET_INT( 0 , "track", &opts.track, "track",
+ BRANCH_TRACK_EXPLICIT),
+ OPT_BOOLEAN('f', NULL, &opts.force, "force"),
+ OPT_BOOLEAN('m', NULL, &opts.merge, "merge"),
+ OPT_END(),
+ };
+
+ memset(&opts, 0, sizeof(opts));
+ memset(&new, 0, sizeof(new));
+
+ git_config(git_checkout_config);
+
+ opts.track = git_branch_track;
+
+ argc = parse_options(argc, argv, options, checkout_usage, 0);
+ if (argc) {
+ arg = argv[0];
+ if (get_sha1(arg, rev))
+ ;
+ else if ((new.commit = lookup_commit_reference_gently(rev, 1))) {
+ new.name = arg;
+ setup_branch_path(&new);
+ if (resolve_ref(new.path, rev, 1, NULL))
+ new.commit = lookup_commit_reference(rev);
+ else
+ new.path = NULL;
+ parse_commit(new.commit);
+ source_tree = new.commit->tree;
+ argv++;
+ argc--;
+ } else if ((source_tree = parse_tree_indirect(rev))) {
+ argv++;
+ argc--;
+ }
+ }
+
+ if (argc && !strcmp(argv[0], "--")) {
+ argv++;
+ argc--;
+ }
+
+ if (!opts.new_branch && (opts.track != git_branch_track))
+ die("git checkout: --track and --no-track require -b");
+
+ if (opts.force && opts.merge)
+ die("git checkout: -f and -m are incompatible");
+
+ if (argc) {
+ const char **pathspec = get_pathspec(prefix, argv);
+ /* Checkout paths */
+ if (opts.new_branch || opts.force || opts.merge) {
+ if (argc == 1) {
+ die("git checkout: updating paths is incompatible with switching branches/forcing\nDid you intend to checkout '%s' which can not be resolved as commit?", argv[0]);
+ } else {
+ die("git checkout: updating paths is incompatible with switching branches/forcing");
+ }
+ }
+
+ if (source_tree)
+ read_tree_some(source_tree, pathspec);
+ else
+ read_cache();
+ return checkout_paths(pathspec);
+ }
+
+ if (new.name && !new.commit) {
+ die("Cannot switch branch to a non-commit.");
+ }
+
+ return switch_branches(&opts, &new);
+}
diff --git a/builtin-commit.c b/builtin-commit.c
index 065e1f7b7..f49c22e64 100644
--- a/builtin-commit.c
+++ b/builtin-commit.c
@@ -205,7 +205,8 @@ static void create_base_index(void)
die("failed to unpack HEAD tree object");
parse_tree(tree);
init_tree_desc(&t, tree->buffer, tree->size);
- unpack_trees(1, &t, &opts);
+ if (unpack_trees(1, &t, &opts))
+ exit(128); /* We've already reported the error, finish dying */
}
static char *prepare_index(int argc, const char **argv, const char *prefix)
diff --git a/builtin-describe.c b/builtin-describe.c
index 342848313..05e309f5a 100644
--- a/builtin-describe.c
+++ b/builtin-describe.c
@@ -46,19 +46,34 @@ static void add_to_known_names(const char *path,
static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
- struct commit *commit = lookup_commit_reference_gently(sha1, 1);
+ int might_be_tag = !prefixcmp(path, "refs/tags/");
+ struct commit *commit;
struct object *object;
- int prio;
+ unsigned char peeled[20];
+ int is_tag, prio;
- if (!commit)
+ if (!all && !might_be_tag)
return 0;
- object = parse_object(sha1);
+
+ if (!peel_ref(path, peeled) && !is_null_sha1(peeled)) {
+ commit = lookup_commit_reference_gently(peeled, 1);
+ if (!commit)
+ return 0;
+ is_tag = !!hashcmp(sha1, commit->object.sha1);
+ } else {
+ commit = lookup_commit_reference_gently(sha1, 1);
+ object = parse_object(sha1);
+ if (!commit || !object)
+ return 0;
+ is_tag = object->type == OBJ_TAG;
+ }
+
/* If --all, then any refs are used.
* If --tags, then any tags are used.
* Otherwise only annotated tags are used.
*/
- if (!prefixcmp(path, "refs/tags/")) {
- if (object->type == OBJ_TAG) {
+ if (might_be_tag) {
+ if (is_tag) {
prio = 2;
if (pattern && fnmatch(pattern, path + 10, 0))
prio = 0;
@@ -159,6 +174,8 @@ static void describe(const char *arg, int last_one)
return;
}
+ if (!max_candidates)
+ die("no tag exactly matches '%s'", sha1_to_hex(cmit->object.sha1));
if (debug)
fprintf(stderr, "searching to describe %s\n", arg);
@@ -255,6 +272,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
OPT_BOOLEAN(0, "all", &all, "use any ref in .git/refs"),
OPT_BOOLEAN(0, "tags", &tags, "use any tag in .git/refs/tags"),
OPT__ABBREV(&abbrev),
+ OPT_SET_INT(0, "exact-match", &max_candidates,
+ "only output exact matches", 0),
OPT_INTEGER(0, "candidates", &max_candidates,
"consider <n> most recent tags (default: 10)"),
OPT_STRING(0, "match", &pattern, "pattern",
@@ -263,8 +282,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix)
};
argc = parse_options(argc, argv, options, describe_usage, 0);
- if (max_candidates < 1)
- max_candidates = 1;
+ if (max_candidates < 0)
+ max_candidates = 0;
else if (max_candidates > MAX_TAGS)
max_candidates = MAX_TAGS;
diff --git a/builtin-diff.c b/builtin-diff.c
index 8f53f52dc..444ff2fd9 100644
--- a/builtin-diff.c
+++ b/builtin-diff.c
@@ -44,12 +44,17 @@ static void stuff_change(struct diff_options *opt,
tmp_u = old_sha1; old_sha1 = new_sha1; new_sha1 = tmp_u;
tmp_c = old_name; old_name = new_name; new_name = tmp_c;
}
+
+ if (opt->prefix &&
+ (strncmp(old_name, opt->prefix, opt->prefix_length) ||
+ strncmp(new_name, opt->prefix, opt->prefix_length)))
+ return;
+
one = alloc_filespec(old_name);
two = alloc_filespec(new_name);
fill_filespec(one, old_sha1, old_mode);
fill_filespec(two, new_sha1, new_mode);
- /* NEEDSWORK: shouldn't this part of diffopt??? */
diff_queue(&diff_queued_diff, one, two);
}
@@ -246,6 +251,10 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
if (diff_setup_done(&rev.diffopt) < 0)
die("diff_setup_done failed");
}
+ if (rev.diffopt.prefix && nongit) {
+ rev.diffopt.prefix = NULL;
+ rev.diffopt.prefix_length = 0;
+ }
DIFF_OPT_SET(&rev.diffopt, ALLOW_EXTERNAL);
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
diff --git a/builtin-fast-export.c b/builtin-fast-export.c
index 49b54de05..e1c56303e 100755
--- a/builtin-fast-export.c
+++ b/builtin-fast-export.c
@@ -123,7 +123,7 @@ static void show_filemodify(struct diff_queue_struct *q,
printf("D %s\n", spec->path);
else {
struct object *object = lookup_object(spec->sha1);
- printf("M 0%06o :%d %s\n", spec->mode,
+ printf("M %06o :%d %s\n", spec->mode,
get_object_mark(object), spec->path);
}
}
diff --git a/builtin-fetch-pack.c b/builtin-fetch-pack.c
index f40135248..5ea48ca7d 100644
--- a/builtin-fetch-pack.c
+++ b/builtin-fetch-pack.c
@@ -538,8 +538,10 @@ static int get_pack(int xd[2], char **pack_lockfile)
cmd.git_cmd = 1;
if (start_command(&cmd))
die("fetch-pack: unable to fork off %s", argv[0]);
- if (do_keep && pack_lockfile)
+ if (do_keep && pack_lockfile) {
*pack_lockfile = index_pack_lockfile(cmd.out);
+ close(cmd.out);
+ }
if (finish_command(&cmd))
die("%s failed", argv[0]);
diff --git a/builtin-for-each-ref.c b/builtin-for-each-ref.c
index f36a43c26..07d9c5721 100644
--- a/builtin-for-each-ref.c
+++ b/builtin-for-each-ref.c
@@ -165,7 +165,7 @@ static int verify_format(const char *format)
for (cp = format; *cp && (sp = find_next(cp)); ) {
const char *ep = strchr(sp, ')');
if (!ep)
- return error("malformatted format string %s", sp);
+ return error("malformed format string %s", sp);
/* sp points at "%(" and ep points at the closing ")" */
parse_atom(sp + 2, ep);
cp = ep + 1;
diff --git a/builtin-init-db.c b/builtin-init-db.c
index 5d7cdda93..79eaf8d6e 100644
--- a/builtin-init-db.c
+++ b/builtin-init-db.c
@@ -29,27 +29,6 @@ static void safe_create_dir(const char *dir, int share)
die("Could not make %s writable by group\n", dir);
}
-static int copy_file(const char *dst, const char *src, int mode)
-{
- int fdi, fdo, status;
-
- mode = (mode & 0111) ? 0777 : 0666;
- if ((fdi = open(src, O_RDONLY)) < 0)
- return fdi;
- if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
- close(fdi);
- return fdo;
- }
- status = copy_fd(fdi, fdo);
- if (close(fdo) != 0)
- return error("%s: write error: %s", dst, strerror(errno));
-
- if (!status && adjust_shared_perm(dst))
- return -1;
-
- return status;
-}
-
static void copy_templates_1(char *path, int baselen,
char *template, int template_baselen,
DIR *dir)
diff --git a/builtin-log.c b/builtin-log.c
index c67d63cb1..3209ea5c6 100644
--- a/builtin-log.c
+++ b/builtin-log.c
@@ -15,6 +15,8 @@
#include "reflog-walk.h"
#include "patch-ids.h"
#include "refs.h"
+#include "run-command.h"
+#include "shortlog.h"
static int default_show_root = 1;
static const char *fmt_patch_subject_prefix = "PATCH";
@@ -428,24 +430,47 @@ static int istitlechar(char c)
(c >= '0' && c <= '9') || c == '.' || c == '_';
}
-static char *extra_headers = NULL;
-static int extra_headers_size = 0;
static const char *fmt_patch_suffix = ".patch";
static int numbered = 0;
static int auto_number = 0;
+static char **extra_hdr;
+static int extra_hdr_nr;
+static int extra_hdr_alloc;
+
+static char **extra_to;
+static int extra_to_nr;
+static int extra_to_alloc;
+
+static char **extra_cc;
+static int extra_cc_nr;
+static int extra_cc_alloc;
+
+static void add_header(const char *value)
+{
+ int len = strlen(value);
+ while (value[len - 1] == '\n')
+ len--;
+ if (!strncasecmp(value, "to: ", 4)) {
+ ALLOC_GROW(extra_to, extra_to_nr + 1, extra_to_alloc);
+ extra_to[extra_to_nr++] = xstrndup(value + 4, len - 4);
+ return;
+ }
+ if (!strncasecmp(value, "cc: ", 4)) {
+ ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+ extra_cc[extra_cc_nr++] = xstrndup(value + 4, len - 4);
+ return;
+ }
+ ALLOC_GROW(extra_hdr, extra_hdr_nr + 1, extra_hdr_alloc);
+ extra_hdr[extra_hdr_nr++] = xstrndup(value, len);
+}
+
static int git_format_config(const char *var, const char *value)
{
if (!strcmp(var, "format.headers")) {
- int len;
-
if (!value)
die("format.headers without value");
- len = strlen(value);
- extra_headers_size += len + 1;
- extra_headers = xrealloc(extra_headers, extra_headers_size);
- extra_headers[extra_headers_size - len - 1] = 0;
- strcat(extra_headers, value);
+ add_header(value);
return 0;
}
if (!strcmp(var, "format.suffix")) {
@@ -470,74 +495,81 @@ static int git_format_config(const char *var, const char *value)
}
+static const char *get_oneline_for_filename(struct commit *commit,
+ int keep_subject)
+{
+ static char filename[PATH_MAX];
+ char *sol;
+ int len = 0;
+ int suffix_len = strlen(fmt_patch_suffix) + 1;
+
+ sol = strstr(commit->buffer, "\n\n");
+ if (!sol)
+ filename[0] = '\0';
+ else {
+ int j, space = 0;
+
+ sol += 2;
+ /* strip [PATCH] or [PATCH blabla] */
+ if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
+ char *eos = strchr(sol + 6, ']');
+ if (eos) {
+ while (isspace(*eos))
+ eos++;
+ sol = eos;
+ }
+ }
+
+ for (j = 0;
+ j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
+ len < sizeof(filename) - suffix_len &&
+ sol[j] && sol[j] != '\n';
+ j++) {
+ if (istitlechar(sol[j])) {
+ if (space) {
+ filename[len++] = '-';
+ space = 0;
+ }
+ filename[len++] = sol[j];
+ if (sol[j] == '.')
+ while (sol[j + 1] == '.')
+ j++;
+ } else
+ space = 1;
+ }
+ while (filename[len - 1] == '.'
+ || filename[len - 1] == '-')
+ len--;
+ filename[len] = '\0';
+ }
+ return filename;
+}
+
static FILE *realstdout = NULL;
static const char *output_directory = NULL;
-static int reopen_stdout(struct commit *commit, int nr, int keep_subject,
- int numbered_files)
+static int reopen_stdout(const char *oneline, int nr, int total)
{
char filename[PATH_MAX];
- char *sol;
int len = 0;
int suffix_len = strlen(fmt_patch_suffix) + 1;
if (output_directory) {
- if (strlen(output_directory) >=
+ len = snprintf(filename, sizeof(filename), "%s",
+ output_directory);
+ if (len >=
sizeof(filename) - FORMAT_PATCH_NAME_MAX - suffix_len)
return error("name of output directory is too long");
- strlcpy(filename, output_directory, sizeof(filename) - suffix_len);
- len = strlen(filename);
if (filename[len - 1] != '/')
filename[len++] = '/';
}
- if (numbered_files) {
- sprintf(filename + len, "%d", nr);
- len = strlen(filename);
-
- } else {
- sprintf(filename + len, "%04d", nr);
- len = strlen(filename);
-
- sol = strstr(commit->buffer, "\n\n");
- if (sol) {
- int j, space = 1;
-
- sol += 2;
- /* strip [PATCH] or [PATCH blabla] */
- if (!keep_subject && !prefixcmp(sol, "[PATCH")) {
- char *eos = strchr(sol + 6, ']');
- if (eos) {
- while (isspace(*eos))
- eos++;
- sol = eos;
- }
- }
-
- for (j = 0;
- j < FORMAT_PATCH_NAME_MAX - suffix_len - 5 &&
- len < sizeof(filename) - suffix_len &&
- sol[j] && sol[j] != '\n';
- j++) {
- if (istitlechar(sol[j])) {
- if (space) {
- filename[len++] = '-';
- space = 0;
- }
- filename[len++] = sol[j];
- if (sol[j] == '.')
- while (sol[j + 1] == '.')
- j++;
- } else
- space = 1;
- }
- while (filename[len - 1] == '.'
- || filename[len - 1] == '-')
- len--;
- filename[len] = 0;
- }
- if (len + suffix_len >= sizeof(filename))
- return error("Patch pathname too long");
+ if (!oneline)
+ len += sprintf(filename + len, "%d", nr);
+ else {
+ len += sprintf(filename + len, "%04d-", nr);
+ len += snprintf(filename + len, sizeof(filename) - len - 1
+ - suffix_len, "%s", oneline);
strcpy(filename + len, fmt_patch_suffix);
}
@@ -594,16 +626,89 @@ static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids, const cha
o2->flags = flags2;
}
-static void gen_message_id(char *dest, unsigned int length, char *base)
+static void gen_message_id(struct rev_info *info, char *base)
{
const char *committer = git_committer_info(IDENT_WARN_ON_NO_NAME);
const char *email_start = strrchr(committer, '<');
const char *email_end = strrchr(committer, '>');
- if(!email_start || !email_end || email_start > email_end - 1)
+ struct strbuf buf;
+ if (!email_start || !email_end || email_start > email_end - 1)
die("Could not extract email from committer identity.");
- snprintf(dest, length, "%s.%lu.git.%.*s", base,
- (unsigned long) time(NULL),
- (int)(email_end - email_start - 1), email_start + 1);
+ strbuf_init(&buf, 0);
+ strbuf_addf(&buf, "%s.%lu.git.%.*s", base,
+ (unsigned long) time(NULL),
+ (int)(email_end - email_start - 1), email_start + 1);
+ info->message_id = strbuf_detach(&buf, NULL);
+}
+
+static void make_cover_letter(struct rev_info *rev, int use_stdout,
+ int numbered, int numbered_files,
+ struct commit *origin,
+ int nr, struct commit **list, struct commit *head)
+{
+ const char *committer;
+ const char *origin_sha1, *head_sha1;
+ const char *argv[7];
+ const char *subject_start = NULL;
+ const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n";
+ const char *msg;
+ const char *extra_headers = rev->extra_headers;
+ struct shortlog log;
+ struct strbuf sb;
+ int i;
+ const char *encoding = "utf-8";
+
+ if (rev->commit_format != CMIT_FMT_EMAIL)
+ die("Cover letter needs email format");
+
+ if (!use_stdout && reopen_stdout(numbered_files ?
+ NULL : "cover-letter", 0, rev->total))
+ return;
+
+ head_sha1 = sha1_to_hex(head->object.sha1);
+
+ log_write_email_headers(rev, head_sha1, &subject_start, &extra_headers);
+
+ committer = git_committer_info(0);
+
+ msg = body;
+ strbuf_init(&sb, 0);
+ pp_user_info(NULL, CMIT_FMT_EMAIL, &sb, committer, DATE_RFC2822,
+ encoding);
+ pp_title_line(CMIT_FMT_EMAIL, &msg, &sb, subject_start, extra_headers,
+ encoding, 0);
+ pp_remainder(CMIT_FMT_EMAIL, &msg, &sb, 0);
+ printf("%s\n", sb.buf);
+
+ strbuf_release(&sb);
+
+ shortlog_init(&log);
+ for (i = 0; i < nr; i++)
+ shortlog_add_commit(&log, list[i]);
+
+ shortlog_output(&log);
+
+ /*
+ * We can only do diffstat with a unique reference point
+ */
+ if (!origin)
+ return;
+
+ origin_sha1 = sha1_to_hex(origin->object.sha1);
+
+ argv[0] = "diff";
+ argv[1] = "--stat";
+ argv[2] = "--summary";
+ argv[3] = head_sha1;
+ argv[4] = "--not";
+ argv[5] = origin_sha1;
+ argv[6] = "--";
+ argv[7] = NULL;
+ fflush(stdout);
+ run_command_v_opt(argv, RUN_GIT_CMD);
+
+ fflush(stdout);
+ printf("\n");
}
static const char *clean_message_id(const char *msg_id)
@@ -641,11 +746,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
int subject_prefix = 0;
int ignore_if_in_upstream = 0;
int thread = 0;
+ int cover_letter = 0;
+ int boundary_count = 0;
+ struct commit *origin = NULL, *head = NULL;
const char *in_reply_to = NULL;
struct patch_ids ids;
char *add_signoff = NULL;
- char message_id[1024];
- char ref_message_id[1024];
+ struct strbuf buf;
git_config(git_format_config);
init_revisions(&rev, prefix);
@@ -658,7 +765,6 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
DIFF_OPT_SET(&rev.diffopt, RECURSIVE);
rev.subject_prefix = fmt_patch_subject_prefix;
- rev.extra_headers = extra_headers;
/*
* Parse the arguments before setup_revisions(), or something
@@ -686,6 +792,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
die("Need a number for --start-number");
start_number = strtol(argv[i], NULL, 10);
}
+ else if (!prefixcmp(argv[i], "--cc=")) {
+ ALLOC_GROW(extra_cc, extra_cc_nr + 1, extra_cc_alloc);
+ extra_cc[extra_cc_nr++] = xstrdup(argv[i] + 5);
+ }
else if (!strcmp(argv[i], "-k") ||
!strcmp(argv[i], "--keep-subject")) {
keep_subject = 1;
@@ -742,11 +852,44 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
rev.subject_prefix = argv[i] + 17;
} else if (!prefixcmp(argv[i], "--suffix="))
fmt_patch_suffix = argv[i] + 9;
+ else if (!strcmp(argv[i], "--cover-letter"))
+ cover_letter = 1;
else
argv[j++] = argv[i];
}
argc = j;
+ strbuf_init(&buf, 0);
+
+ for (i = 0; i < extra_hdr_nr; i++) {
+ strbuf_addstr(&buf, extra_hdr[i]);
+ strbuf_addch(&buf, '\n');
+ }
+
+ if (extra_to_nr)
+ strbuf_addstr(&buf, "To: ");
+ for (i = 0; i < extra_to_nr; i++) {
+ if (i)
+ strbuf_addstr(&buf, " ");
+ strbuf_addstr(&buf, extra_to[i]);
+ if (i + 1 < extra_to_nr)
+ strbuf_addch(&buf, ',');
+ strbuf_addch(&buf, '\n');
+ }
+
+ if (extra_cc_nr)
+ strbuf_addstr(&buf, "Cc: ");
+ for (i = 0; i < extra_cc_nr; i++) {
+ if (i)
+ strbuf_addstr(&buf, " ");
+ strbuf_addstr(&buf, extra_cc[i]);
+ if (i + 1 < extra_cc_nr)
+ strbuf_addch(&buf, ',');
+ strbuf_addch(&buf, '\n');
+ }
+
+ rev.extra_headers = strbuf_detach(&buf, 0);
+
if (start_number < 0)
start_number = 1;
if (numbered && keep_subject)
@@ -793,6 +936,18 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
* get_revision() to do the usual traversal.
*/
}
+ if (cover_letter) {
+ /* remember the range */
+ int i;
+ for (i = 0; i < rev.pending.nr; i++) {
+ struct object *o = rev.pending.objects[i].item;
+ if (!(o->flags & UNINTERESTING))
+ head = (struct commit *)o;
+ }
+ /* We can't generate a cover letter without any patches */
+ if (!head)
+ return 0;
+ }
if (ignore_if_in_upstream)
get_patch_ids(&rev, &ids, prefix);
@@ -802,7 +957,15 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (prepare_revision_walk(&rev))
die("revision walk setup failed");
+ rev.boundary = 1;
while ((commit = get_revision(&rev)) != NULL) {
+ if (commit->object.flags & BOUNDARY) {
+ fprintf(stderr, "Boundary %s\n", sha1_to_hex(commit->object.sha1));
+ boundary_count++;
+ origin = (boundary_count == 1) ? commit : NULL;
+ continue;
+ }
+
/* ignore merges */
if (commit->parents && commit->parents->next)
continue;
@@ -820,29 +983,42 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
numbered = 1;
if (numbered)
rev.total = total + start_number - 1;
- rev.add_signoff = add_signoff;
if (in_reply_to)
rev.ref_message_id = clean_message_id(in_reply_to);
+ if (cover_letter) {
+ if (thread)
+ gen_message_id(&rev, "cover");
+ make_cover_letter(&rev, use_stdout, numbered, numbered_files,
+ origin, nr, list, head);
+ total++;
+ start_number--;
+ }
+ rev.add_signoff = add_signoff;
while (0 <= --nr) {
int shown;
commit = list[nr];
rev.nr = total - nr + (start_number - 1);
/* Make the second and subsequent mails replies to the first */
if (thread) {
- if (nr == (total - 2)) {
- strncpy(ref_message_id, message_id,
- sizeof(ref_message_id));
- ref_message_id[sizeof(ref_message_id)-1]='\0';
- rev.ref_message_id = ref_message_id;
+ /* Have we already had a message ID? */
+ if (rev.message_id) {
+ /*
+ * If we've got the ID to be a reply
+ * to, discard the current ID;
+ * otherwise, make everything a reply
+ * to that.
+ */
+ if (rev.ref_message_id)
+ free(rev.message_id);
+ else
+ rev.ref_message_id = rev.message_id;
}
- gen_message_id(message_id, sizeof(message_id),
- sha1_to_hex(commit->object.sha1));
- rev.message_id = message_id;
+ gen_message_id(&rev, sha1_to_hex(commit->object.sha1));
}
- if (!use_stdout)
- if (reopen_stdout(commit, rev.nr, keep_subject,
- numbered_files))
- die("Failed to create output files");
+ if (!use_stdout && reopen_stdout(numbered_files ? NULL :
+ get_oneline_for_filename(commit, keep_subject),
+ rev.nr, rev.total))
+ die("Failed to create output files");
shown = log_tree_commit(&rev, commit);
free(commit->buffer);
commit->buffer = NULL;
diff --git a/builtin-merge-file.c b/builtin-merge-file.c
index 58deb62ac..adce6d463 100644
--- a/builtin-merge-file.c
+++ b/builtin-merge-file.c
@@ -46,7 +46,7 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix)
}
ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2],
- &xpp, XDL_MERGE_ZEALOUS, &result);
+ &xpp, XDL_MERGE_ZEALOUS_ALNUM, &result);
for (i = 0; i < 3; i++)
free(mmfs[i].ptr);
diff --git a/merge-recursive.c b/builtin-merge-recursive.c
index 55ef76f5a..6fe4102c0 100644
--- a/merge-recursive.c
+++ b/builtin-merge-recursive.c
@@ -7,6 +7,7 @@
#include "cache-tree.h"
#include "commit.h"
#include "blob.h"
+#include "builtin.h"
#include "tree-walk.h"
#include "diff.h"
#include "diffcore.h"
@@ -17,6 +18,7 @@
#include "xdiff-interface.h"
#include "interpolate.h"
#include "attr.h"
+#include "merge-recursive.h"
static int subtree_merge;
@@ -221,22 +223,11 @@ static int git_merge_trees(int index_only,
return rc;
}
-static int unmerged_index(void)
-{
- int i;
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (ce_stage(ce))
- return 1;
- }
- return 0;
-}
-
-static struct tree *git_write_tree(void)
+struct tree *write_tree_from_memory(void)
{
struct tree *result = NULL;
- if (unmerged_index()) {
+ if (unmerged_cache()) {
int i;
output(0, "There are unmerged index entries:");
for (i = 0; i < active_nr; i++) {
@@ -1496,12 +1487,12 @@ static int process_entry(const char *path, struct stage_data *entry,
return clean_merge;
}
-static int merge_trees(struct tree *head,
- struct tree *merge,
- struct tree *common,
- const char *branch1,
- const char *branch2,
- struct tree **result)
+int merge_trees(struct tree *head,
+ struct tree *merge,
+ struct tree *common,
+ const char *branch1,
+ const char *branch2,
+ struct tree **result)
{
int code, clean;
@@ -1523,7 +1514,7 @@ static int merge_trees(struct tree *head,
sha1_to_hex(head->object.sha1),
sha1_to_hex(merge->object.sha1));
- if (unmerged_index()) {
+ if (unmerged_cache()) {
struct path_list *entries, *re_head, *re_merge;
int i;
path_list_clear(&current_file_set, 1);
@@ -1553,7 +1544,7 @@ static int merge_trees(struct tree *head,
clean = 1;
if (index_only)
- *result = git_write_tree();
+ *result = write_tree_from_memory();
return clean;
}
@@ -1573,12 +1564,12 @@ static struct commit_list *reverse_commit_list(struct commit_list *list)
* Merge the commits h1 and h2, return the resulting virtual
* commit object and a flag indicating the cleanness of the merge.
*/
-static int merge(struct commit *h1,
- struct commit *h2,
- const char *branch1,
- const char *branch2,
- struct commit_list *ca,
- struct commit **result)
+int merge_recursive(struct commit *h1,
+ struct commit *h2,
+ const char *branch1,
+ const char *branch2,
+ struct commit_list *ca,
+ struct commit **result)
{
struct commit_list *iter;
struct commit *merged_common_ancestors;
@@ -1623,11 +1614,11 @@ static int merge(struct commit *h1,
* "conflicts" were already resolved.
*/
discard_cache();
- merge(merged_common_ancestors, iter->item,
- "Temporary merge branch 1",
- "Temporary merge branch 2",
- NULL,
- &merged_common_ancestors);
+ merge_recursive(merged_common_ancestors, iter->item,
+ "Temporary merge branch 1",
+ "Temporary merge branch 2",
+ NULL,
+ &merged_common_ancestors);
call_depth--;
if (!merged_common_ancestors)
@@ -1698,7 +1689,7 @@ static int merge_config(const char *var, const char *value)
return git_default_config(var, value);
}
-int main(int argc, char *argv[])
+int cmd_merge_recursive(int argc, const char **argv, const char *prefix)
{
static const char *bases[20];
static unsigned bases_count = 0;
@@ -1752,7 +1743,7 @@ int main(int argc, char *argv[])
struct commit *ancestor = get_ref(bases[i]);
ca = commit_list_insert(ancestor, &ca);
}
- clean = merge(h1, h2, branch1, branch2, ca, &result);
+ clean = merge_recursive(h1, h2, branch1, branch2, ca, &result);
if (active_cache_changed &&
(write_cache(index_fd, active_cache, active_nr) ||
diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c
index 7dff6536d..1bba6e6a6 100644
--- a/builtin-pack-objects.c
+++ b/builtin-pack-objects.c
@@ -16,6 +16,7 @@
#include "progress.h"
#ifdef THREADED_DELTA_SEARCH
+#include "thread-utils.h"
#include <pthread.h>
#endif
@@ -1851,11 +1852,11 @@ static int git_pack_config(const char *k, const char *v)
}
if (!strcmp(k, "pack.threads")) {
delta_search_threads = git_config_int(k, v);
- if (delta_search_threads < 1)
+ if (delta_search_threads < 0)
die("invalid number of threads specified (%d)",
delta_search_threads);
#ifndef THREADED_DELTA_SEARCH
- if (delta_search_threads > 1)
+ if (delta_search_threads != 1)
warning("no threads support, ignoring %s", k);
#endif
return 0;
@@ -2121,10 +2122,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (!prefixcmp(arg, "--threads=")) {
char *end;
delta_search_threads = strtoul(arg+10, &end, 0);
- if (!arg[10] || *end || delta_search_threads < 1)
+ if (!arg[10] || *end || delta_search_threads < 0)
usage(pack_usage);
#ifndef THREADED_DELTA_SEARCH
- if (delta_search_threads > 1)
+ if (delta_search_threads != 1)
warning("no threads support, "
"ignoring %s", arg);
#endif
@@ -2234,6 +2235,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
if (!pack_to_stdout && thin)
die("--thin cannot be used to build an indexable pack.");
+#ifdef THREADED_DELTA_SEARCH
+ if (!delta_search_threads) /* --threads=0 means autodetect */
+ delta_search_threads = online_cpus();
+#endif
+
prepare_packed_git();
if (progress)
diff --git a/builtin-push.c b/builtin-push.c
index 9f727c00f..b68c6813b 100644
--- a/builtin-push.c
+++ b/builtin-push.c
@@ -44,15 +44,6 @@ static void set_refspecs(const char **refs, int nr)
strcat(tag, refs[i]);
ref = tag;
}
- if (!strcmp("HEAD", ref)) {
- unsigned char sha1_dummy[20];
- ref = resolve_ref(ref, sha1_dummy, 1, NULL);
- if (!ref)
- die("HEAD cannot be resolved.");
- if (prefixcmp(ref, "refs/heads/"))
- die("HEAD cannot be resolved to branch.");
- ref = xstrdup(ref + 11);
- }
add_refspec(ref);
}
}
diff --git a/builtin-read-tree.c b/builtin-read-tree.c
index 726fb0b58..0138f5a91 100644
--- a/builtin-read-tree.c
+++ b/builtin-read-tree.c
@@ -41,6 +41,7 @@ static int read_cache_unmerged(void)
for (i = 0; i < active_nr; i++) {
struct cache_entry *ce = active_cache[i];
if (ce_stage(ce)) {
+ remove_index_entry(ce);
if (last && !strcmp(ce->name, last->name))
continue;
cache_tree_invalidate_path(active_cache_tree, ce->name);
@@ -268,7 +269,8 @@ int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
parse_tree(tree);
init_tree_desc(t+i, tree->buffer, tree->size);
}
- unpack_trees(nr_trees, t, &opts);
+ if (unpack_trees(nr_trees, t, &opts))
+ return 128;
/*
* When reading only one tree (either the most basic form,
diff --git a/builtin-reflog.c b/builtin-reflog.c
index 4836ec951..ab53c8cb7 100644
--- a/builtin-reflog.c
+++ b/builtin-reflog.c
@@ -276,10 +276,11 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused,
for_each_reflog_ent(ref, expire_reflog_ent, &cb);
finish:
if (cb.newlog) {
- if (fclose(cb.newlog))
+ if (fclose(cb.newlog)) {
status |= error("%s: %s", strerror(errno),
newlog_path);
- if (rename(newlog_path, log_file)) {
+ unlink(newlog_path);
+ } else if (rename(newlog_path, log_file)) {
status |= error("cannot rename %s to %s",
newlog_path, log_file);
unlink(newlog_path);
diff --git a/builtin-rerere.c b/builtin-rerere.c
index b0c17bde8..c607aade6 100644
--- a/builtin-rerere.c
+++ b/builtin-rerere.c
@@ -267,23 +267,6 @@ static int diff_two(const char *file1, const char *label1,
return 0;
}
-static int copy_file(const char *src, const char *dest)
-{
- FILE *in, *out;
- char buffer[32768];
- int count;
-
- if (!(in = fopen(src, "r")))
- return error("Could not open %s", src);
- if (!(out = fopen(dest, "w")))
- return error("Could not open %s", dest);
- while ((count = fread(buffer, 1, sizeof(buffer), in)))
- fwrite(buffer, 1, count, out);
- fclose(in);
- fclose(out);
- return 0;
-}
-
static int do_plain_rerere(struct path_list *rr, int fd)
{
struct path_list conflict = { NULL, 0, 0, 1 };
@@ -343,7 +326,7 @@ static int do_plain_rerere(struct path_list *rr, int fd)
continue;
fprintf(stderr, "Recorded resolution for '%s'.\n", path);
- copy_file(path, rr_path(name, "postimage"));
+ copy_file(rr_path(name, "postimage"), path, 0666);
tail_optimization:
if (i < rr->nr - 1)
memmove(rr->items + i,
diff --git a/builtin-reset.c b/builtin-reset.c
index 7ee811f0b..af0037ec6 100644
--- a/builtin-reset.c
+++ b/builtin-reset.c
@@ -16,6 +16,7 @@
#include "diff.h"
#include "diffcore.h"
#include "tree.h"
+#include "branch.h"
static const char builtin_reset_usage[] =
"git-reset [--mixed | --soft | --hard] [-q] [<commit-ish>] [ [--] <paths>...]";
@@ -44,18 +45,6 @@ static inline int is_merge(void)
return !access(git_path("MERGE_HEAD"), F_OK);
}
-static int unmerged_files(void)
-{
- int i;
- read_cache();
- for (i = 0; i < active_nr; i++) {
- struct cache_entry *ce = active_cache[i];
- if (ce_stage(ce))
- return 1;
- }
- return 0;
-}
-
static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
{
int i = 0;
@@ -250,7 +239,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
* at all, but requires them in a good order. Other resets reset
* the index file to the tree object we are switching to. */
if (reset_type == SOFT) {
- if (is_merge() || unmerged_files())
+ if (is_merge() || read_cache() < 0 || unmerged_cache())
die("Cannot do a soft reset in the middle of a merge.");
}
else if (reset_index_file(sha1, (reset_type == HARD)))
@@ -282,10 +271,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
break;
}
- unlink(git_path("MERGE_HEAD"));
- unlink(git_path("rr-cache/MERGE_RR"));
- unlink(git_path("MERGE_MSG"));
- unlink(git_path("SQUASH_MSG"));
+ remove_branch_state();
free(reflog_action);
diff --git a/builtin-rev-parse.c b/builtin-rev-parse.c
index b9af1a5a5..90dbb9d7c 100644
--- a/builtin-rev-parse.c
+++ b/builtin-rev-parse.c
@@ -315,7 +315,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)
s = strchr(sb.buf, ' ');
if (!s || *sb.buf == ' ') {
o->type = OPTION_GROUP;
- o->help = xstrdup(skipspaces(s));
+ o->help = xstrdup(skipspaces(sb.buf));
continue;
}
diff --git a/builtin-send-pack.c b/builtin-send-pack.c
index 8afb1d0bc..b0cfae83f 100644
--- a/builtin-send-pack.c
+++ b/builtin-send-pack.c
@@ -71,6 +71,7 @@ static int pack_objects(int fd, struct ref *refs)
refs = refs->next;
}
+ close(po.in);
if (finish_command(&po))
return error("pack-objects died with strange error");
return 0;
@@ -403,12 +404,15 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
if (!remote_tail)
remote_tail = &remote_refs;
if (match_refs(local_refs, remote_refs, &remote_tail,
- nr_refspec, refspec, flags))
+ nr_refspec, refspec, flags)) {
+ close(out);
return -1;
+ }
if (!remote_refs) {
fprintf(stderr, "No refs in common and none specified; doing nothing.\n"
"Perhaps you should specify a branch such as 'master'.\n");
+ close(out);
return 0;
}
@@ -495,12 +499,11 @@ static int do_send_pack(int in, int out, struct remote *remote, const char *dest
packet_flush(out);
if (new_refs && !args.dry_run) {
- if (pack_objects(out, remote_refs) < 0) {
- close(out);
+ if (pack_objects(out, remote_refs) < 0)
return -1;
- }
}
- close(out);
+ else
+ close(out);
if (expect_status_report)
ret = receive_status(in, remote_refs);
@@ -648,7 +651,7 @@ int send_pack(struct send_pack_args *my_args,
conn = git_connect(fd, dest, args.receivepack, args.verbose ? CONNECT_VERBOSE : 0);
ret = do_send_pack(fd[0], fd[1], remote, dest, nr_heads, heads);
close(fd[0]);
- close(fd[1]);
+ /* do_send_pack always closes fd[1] */
ret |= finish_connect(conn);
return !!ret;
}
diff --git a/builtin-shortlog.c b/builtin-shortlog.c
index 0055a57ae..af31abaaf 100644
--- a/builtin-shortlog.c
+++ b/builtin-shortlog.c
@@ -6,13 +6,11 @@
#include "revision.h"
#include "utf8.h"
#include "mailmap.h"
+#include "shortlog.h"
static const char shortlog_usage[] =
"git-shortlog [-n] [-s] [-e] [<commit-id>... ]";
-static char *common_repo_prefix;
-static int email;
-
static int compare_by_number(const void *a1, const void *a2)
{
const struct path_list_item *i1 = a1, *i2 = a2;
@@ -26,13 +24,11 @@ static int compare_by_number(const void *a1, const void *a2)
return -1;
}
-static struct path_list mailmap = {NULL, 0, 0, 0};
-
-static void insert_one_record(struct path_list *list,
+static void insert_one_record(struct shortlog *log,
const char *author,
const char *oneline)
{
- const char *dot3 = common_repo_prefix;
+ const char *dot3 = log->common_repo_prefix;
char *buffer, *p;
struct path_list_item *item;
struct path_list *onelines;
@@ -47,7 +43,7 @@ static void insert_one_record(struct path_list *list,
eoemail = strchr(boemail, '>');
if (!eoemail)
return;
- if (!map_email(&mailmap, boemail+1, namebuf, sizeof(namebuf))) {
+ if (!map_email(&log->mailmap, boemail+1, namebuf, sizeof(namebuf))) {
while (author < boemail && isspace(*author))
author++;
for (len = 0;
@@ -61,14 +57,14 @@ static void insert_one_record(struct path_list *list,
else
len = strlen(namebuf);
- if (email) {
+ if (log->email) {
size_t room = sizeof(namebuf) - len - 1;
int maillen = eoemail - boemail + 1;
snprintf(namebuf + len, room, " %.*s", maillen, boemail);
}
buffer = xstrdup(namebuf);
- item = path_list_insert(buffer, list);
+ item = path_list_insert(buffer, &log->list);
if (item->util == NULL)
item->util = xcalloc(1, sizeof(struct path_list));
else
@@ -114,7 +110,7 @@ static void insert_one_record(struct path_list *list,
onelines->items[onelines->nr++].path = buffer;
}
-static void read_from_stdin(struct path_list *list)
+static void read_from_stdin(struct shortlog *log)
{
char author[1024], oneline[1024];
@@ -128,39 +124,43 @@ static void read_from_stdin(struct path_list *list)
while (fgets(oneline, sizeof(oneline), stdin) &&
oneline[0] == '\n')
; /* discard blanks */
- insert_one_record(list, author + 8, oneline);
+ insert_one_record(log, author + 8, oneline);
}
}
-static void get_from_rev(struct rev_info *rev, struct path_list *list)
+void shortlog_add_commit(struct shortlog *log, struct commit *commit)
{
- struct commit *commit;
-
- if (prepare_revision_walk(rev))
- die("revision walk setup failed");
- while ((commit = get_revision(rev)) != NULL) {
- const char *author = NULL, *buffer;
+ const char *author = NULL, *buffer;
- buffer = commit->buffer;
- while (*buffer && *buffer != '\n') {
- const char *eol = strchr(buffer, '\n');
+ buffer = commit->buffer;
+ while (*buffer && *buffer != '\n') {
+ const char *eol = strchr(buffer, '\n');
- if (eol == NULL)
- eol = buffer + strlen(buffer);
- else
- eol++;
+ if (eol == NULL)
+ eol = buffer + strlen(buffer);
+ else
+ eol++;
- if (!prefixcmp(buffer, "author "))
- author = buffer + 7;
- buffer = eol;
- }
- if (!author)
- die("Missing author: %s",
- sha1_to_hex(commit->object.sha1));
- if (*buffer)
- buffer++;
- insert_one_record(list, author, !*buffer ? "<none>" : buffer);
+ if (!prefixcmp(buffer, "author "))
+ author = buffer + 7;
+ buffer = eol;
}
+ if (!author)
+ die("Missing author: %s",
+ sha1_to_hex(commit->object.sha1));
+ if (*buffer)
+ buffer++;
+ insert_one_record(log, author, !*buffer ? "<none>" : buffer);
+}
+
+static void get_from_rev(struct rev_info *rev, struct shortlog *log)
+{
+ struct commit *commit;
+
+ if (prepare_revision_walk(rev))
+ die("revision walk setup failed");
+ while ((commit = get_revision(rev)) != NULL)
+ shortlog_add_commit(log, commit);
}
static int parse_uint(char const **arg, int comma)
@@ -212,29 +212,38 @@ static void parse_wrap_args(const char *arg, int *in1, int *in2, int *wrap)
die(wrap_arg_usage);
}
+void shortlog_init(struct shortlog *log)
+{
+ memset(log, 0, sizeof(*log));
+
+ read_mailmap(&log->mailmap, ".mailmap", &log->common_repo_prefix);
+
+ log->list.strdup_paths = 1;
+ log->wrap = DEFAULT_WRAPLEN;
+ log->in1 = DEFAULT_INDENT1;
+ log->in2 = DEFAULT_INDENT2;
+}
+
int cmd_shortlog(int argc, const char **argv, const char *prefix)
{
+ struct shortlog log;
struct rev_info rev;
- struct path_list list = { NULL, 0, 0, 1 };
- int i, j, sort_by_number = 0, summary = 0;
- int wrap_lines = 0;
- int wrap = DEFAULT_WRAPLEN;
- int in1 = DEFAULT_INDENT1;
- int in2 = DEFAULT_INDENT2;
+
+ shortlog_init(&log);
/* since -n is a shadowed rev argument, parse our args first */
while (argc > 1) {
if (!strcmp(argv[1], "-n") || !strcmp(argv[1], "--numbered"))
- sort_by_number = 1;
+ log.sort_by_number = 1;
else if (!strcmp(argv[1], "-s") ||
!strcmp(argv[1], "--summary"))
- summary = 1;
+ log.summary = 1;
else if (!strcmp(argv[1], "-e") ||
!strcmp(argv[1], "--email"))
- email = 1;
+ log.email = 1;
else if (!prefixcmp(argv[1], "-w")) {
- wrap_lines = 1;
- parse_wrap_args(argv[1], &in1, &in2, &wrap);
+ log.wrap_lines = 1;
+ parse_wrap_args(argv[1], &log.in1, &log.in2, &log.wrap);
}
else if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
usage(shortlog_usage);
@@ -248,34 +257,38 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
if (argc > 1)
die ("unrecognized argument: %s", argv[1]);
- read_mailmap(&mailmap, ".mailmap", &common_repo_prefix);
-
/* assume HEAD if from a tty */
if (!rev.pending.nr && isatty(0))
add_head_to_pending(&rev);
if (rev.pending.nr == 0) {
- read_from_stdin(&list);
+ read_from_stdin(&log);
}
else
- get_from_rev(&rev, &list);
+ get_from_rev(&rev, &log);
- if (sort_by_number)
- qsort(list.items, list.nr, sizeof(struct path_list_item),
- compare_by_number);
+ shortlog_output(&log);
+ return 0;
+}
- for (i = 0; i < list.nr; i++) {
- struct path_list *onelines = list.items[i].util;
+void shortlog_output(struct shortlog *log)
+{
+ int i, j;
+ if (log->sort_by_number)
+ qsort(log->list.items, log->list.nr, sizeof(struct path_list_item),
+ compare_by_number);
+ for (i = 0; i < log->list.nr; i++) {
+ struct path_list *onelines = log->list.items[i].util;
- if (summary) {
- printf("%6d\t%s\n", onelines->nr, list.items[i].path);
+ if (log->summary) {
+ printf("%6d\t%s\n", onelines->nr, log->list.items[i].path);
} else {
- printf("%s (%d):\n", list.items[i].path, onelines->nr);
+ printf("%s (%d):\n", log->list.items[i].path, onelines->nr);
for (j = onelines->nr - 1; j >= 0; j--) {
const char *msg = onelines->items[j].path;
- if (wrap_lines) {
- int col = print_wrapped_text(msg, in1, in2, wrap);
- if (col != wrap)
+ if (log->wrap_lines) {
+ int col = print_wrapped_text(msg, log->in1, log->in2, log->wrap);
+ if (col != log->wrap)
putchar('\n');
}
else
@@ -287,13 +300,11 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)
onelines->strdup_paths = 1;
path_list_clear(onelines, 1);
free(onelines);
- list.items[i].util = NULL;
+ log->list.items[i].util = NULL;
}
- list.strdup_paths = 1;
- path_list_clear(&list, 1);
- mailmap.strdup_paths = 1;
- path_list_clear(&mailmap, 1);
-
- return 0;
+ log->list.strdup_paths = 1;
+ path_list_clear(&log->list, 1);
+ log->mailmap.strdup_paths = 1;
+ path_list_clear(&log->mailmap, 1);
}
diff --git a/builtin-tag.c b/builtin-tag.c
index 716b4fff3..28c36fdcd 100644
--- a/builtin-tag.c
+++ b/builtin-tag.c
@@ -226,12 +226,13 @@ static int do_sign(struct strbuf *buffer)
if (write_in_full(gpg.in, buffer->buf, buffer->len) != buffer->len) {
close(gpg.in);
+ close(gpg.out);
finish_command(&gpg);
return error("gpg did not accept the tag data");
}
close(gpg.in);
- gpg.close_in = 0;
len = strbuf_read(buffer, gpg.out, 1024);
+ close(gpg.out);
if (finish_command(&gpg) || !len || len < 0)
return error("gpg failed to sign the tag");
diff --git a/builtin-verify-tag.c b/builtin-verify-tag.c
index cc4c55d7e..f3ef11fa2 100644
--- a/builtin-verify-tag.c
+++ b/builtin-verify-tag.c
@@ -45,14 +45,12 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
memset(&gpg, 0, sizeof(gpg));
gpg.argv = args_gpg;
gpg.in = -1;
- gpg.out = 1;
args_gpg[2] = path;
if (start_command(&gpg))
return error("could not run gpg.");
write_in_full(gpg.in, buf, len);
close(gpg.in);
- gpg.close_in = 0;
ret = finish_command(&gpg);
unlink(path);
diff --git a/builtin.h b/builtin.h
index 3d1628c59..674c8a141 100644
--- a/builtin.h
+++ b/builtin.h
@@ -18,6 +18,7 @@ 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);
extern int cmd_cat_file(int argc, const char **argv, const char *prefix);
+extern int cmd_checkout(int argc, const char **argv, const char *prefix);
extern int cmd_checkout_index(int argc, const char **argv, const char *prefix);
extern int cmd_check_attr(int argc, const char **argv, const char *prefix);
extern int cmd_check_ref_format(int argc, const char **argv, const char *prefix);
@@ -56,6 +57,7 @@ extern int cmd_mailsplit(int argc, const char **argv, const char *prefix);
extern int cmd_merge_base(int argc, const char **argv, const char *prefix);
extern int cmd_merge_ours(int argc, const char **argv, const char *prefix);
extern int cmd_merge_file(int argc, const char **argv, const char *prefix);
+extern int cmd_merge_recursive(int argc, const char **argv, const char *prefix);
extern int cmd_mv(int argc, const char **argv, const char *prefix);
extern int cmd_name_rev(int argc, const char **argv, const char *prefix);
extern int cmd_pack_objects(int argc, const char **argv, const char *prefix);
diff --git a/bundle.c b/bundle.c
index bd12ec853..0ba5df17e 100644
--- a/bundle.c
+++ b/bundle.c
@@ -333,10 +333,12 @@ int create_bundle(struct bundle_header *header, const char *path,
write_or_die(rls.in, sha1_to_hex(object->sha1), 40);
write_or_die(rls.in, "\n", 1);
}
+ close(rls.in);
if (finish_command(&rls))
return error ("pack-objects died");
-
- return bundle_to_stdout ? close(bundle_fd) : commit_lock_file(&lock);
+ if (!bundle_to_stdout)
+ commit_lock_file(&lock);
+ return 0;
}
int unbundle(struct bundle_header *header, int bundle_fd)
diff --git a/cache.h b/cache.h
index e1000bccb..f16d341f5 100644
--- a/cache.h
+++ b/cache.h
@@ -110,7 +110,6 @@ struct ondisk_cache_entry {
};
struct cache_entry {
- struct cache_entry *next;
unsigned int ce_ctime;
unsigned int ce_mtime;
unsigned int ce_dev;
@@ -121,6 +120,7 @@ struct cache_entry {
unsigned int ce_size;
unsigned int ce_flags;
unsigned char sha1[20];
+ struct cache_entry *next;
char name[FLEX_ARRAY]; /* more */
};
@@ -133,7 +133,39 @@ struct cache_entry {
#define CE_UPDATE (0x10000)
#define CE_REMOVE (0x20000)
#define CE_UPTODATE (0x40000)
-#define CE_UNHASHED (0x80000)
+
+#define CE_HASHED (0x100000)
+#define CE_UNHASHED (0x200000)
+
+/*
+ * Copy the sha1 and stat state of a cache entry from one to
+ * another. But we never change the name, or the hash state!
+ */
+#define CE_STATE_MASK (CE_HASHED | CE_UNHASHED)
+static inline void copy_cache_entry(struct cache_entry *dst, struct cache_entry *src)
+{
+ unsigned int state = dst->ce_flags & CE_STATE_MASK;
+
+ /* Don't copy hash chain and name */
+ memcpy(dst, src, offsetof(struct cache_entry, next));
+
+ /* Restore the hash state */
+ dst->ce_flags = (dst->ce_flags & ~CE_STATE_MASK) | state;
+}
+
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+static inline void remove_index_entry(struct cache_entry *ce)
+{
+ ce->ce_flags |= CE_UNHASHED;
+}
static inline unsigned create_ce_flags(size_t len, unsigned stage)
{
@@ -220,6 +252,7 @@ extern struct index_state the_index;
#define read_cache_from(path) read_index_from(&the_index, (path))
#define write_cache(newfd, cache, entries) write_index(&the_index, (newfd))
#define discard_cache() discard_index(&the_index)
+#define unmerged_cache() unmerged_index(&the_index)
#define cache_name_pos(name, namelen) index_name_pos(&the_index,(name),(namelen))
#define add_cache_entry(ce, option) add_index_entry(&the_index, (ce), (option))
#define remove_cache_entry_at(pos) remove_index_entry_at(&the_index, (pos))
@@ -314,6 +347,7 @@ extern int read_index(struct index_state *);
extern int read_index_from(struct index_state *, const char *path);
extern int write_index(struct index_state *, int newfd);
extern int discard_index(struct index_state *);
+extern int unmerged_index(struct index_state *);
extern int verify_path(const char *path);
extern int index_name_exists(struct index_state *istate, const char *name, int namelen);
extern int index_name_pos(struct index_state *, const char *name, int namelen);
@@ -391,6 +425,15 @@ enum safe_crlf {
extern enum safe_crlf safe_crlf;
+enum branch_track {
+ BRANCH_TRACK_NEVER = 0,
+ BRANCH_TRACK_REMOTE,
+ BRANCH_TRACK_ALWAYS,
+ BRANCH_TRACK_EXPLICIT,
+};
+
+extern enum branch_track git_branch_track;
+
#define GIT_REPO_VERSION 0
extern int repository_format_version;
extern int check_repository_format(void);
@@ -666,6 +709,7 @@ extern const char *git_log_output_encoding;
/* IO helper functions */
extern void maybe_flush_or_die(FILE *, const char *);
extern int copy_fd(int ifd, int ofd);
+extern int copy_file(const char *dst, const char *src, int mode);
extern int read_in_full(int fd, void *buf, size_t count);
extern int write_in_full(int fd, const void *buf, size_t count);
extern void write_or_die(int fd, const void *buf, size_t count);
@@ -719,6 +763,7 @@ void shift_tree(const unsigned char *, const unsigned char *, unsigned char *, i
#define WS_TRAILING_SPACE 01
#define WS_SPACE_BEFORE_TAB 02
#define WS_INDENT_WITH_NON_TAB 04
+#define WS_CR_AT_EOL 010
#define WS_DEFAULT_RULE (WS_TRAILING_SPACE|WS_SPACE_BEFORE_TAB)
extern unsigned whitespace_rule_cfg;
extern unsigned whitespace_rule(const char *);
@@ -727,10 +772,13 @@ extern unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
FILE *stream, const char *set,
const char *reset, const char *ws);
extern char *whitespace_error_string(unsigned ws);
+extern int ws_fix_copy(char *, const char *, int, unsigned, int *);
/* ls-files */
int pathspec_match(const char **spec, char *matched, const char *filename, int skiplen);
int report_path_error(const char *ps_matched, const char **pathspec, int prefix_offset);
void overlay_tree_on_cache(const char *tree_name, const char *prefix);
+char *alias_lookup(const char *alias);
+
#endif /* CACHE_H */
diff --git a/commit.h b/commit.h
index 10e2b5d4c..80d65b96d 100644
--- a/commit.h
+++ b/commit.h
@@ -71,6 +71,21 @@ extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
int abbrev, const char *subject,
const char *after_subject, enum date_mode,
int non_ascii_present);
+void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
+ const char *line, enum date_mode dmode,
+ const char *encoding);
+void pp_title_line(enum cmit_fmt fmt,
+ const char **msg_p,
+ struct strbuf *sb,
+ const char *subject,
+ const char *after_subject,
+ const char *encoding,
+ int plain_non_ascii);
+void pp_remainder(enum cmit_fmt fmt,
+ const char **msg_p,
+ struct strbuf *sb,
+ int indent);
+
/** Removes the first commit from a list sorted by date, and adds all
* of its parents.
diff --git a/config.c b/config.c
index cba2bcfb6..062449459 100644
--- a/config.c
+++ b/config.c
@@ -471,6 +471,14 @@ int git_default_config(const char *var, const char *value)
whitespace_rule_cfg = parse_whitespace_rule(value);
return 0;
}
+ if (!strcmp(var, "branch.autosetupmerge")) {
+ if (value && !strcasecmp(value, "always")) {
+ git_branch_track = BRANCH_TRACK_ALWAYS;
+ return 0;
+ }
+ git_branch_track = git_config_bool(var, value);
+ return 0;
+ }
/* Add other config variables here and to Documentation/config.txt. */
return 0;
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 4ea727b14..8722a6879 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -91,7 +91,10 @@ __git_ps1 ()
fi
if ! b="$(git symbolic-ref HEAD 2>/dev/null)"
then
- b="$(cut -c1-7 $g/HEAD)..."
+ if ! b="$(git describe --exact-match HEAD 2>/dev/null)"
+ then
+ b="$(cut -c1-7 $g/HEAD)..."
+ fi
fi
fi
diff --git a/git-checkout.sh b/contrib/examples/git-checkout.sh
index bd74d701a..1a7689a48 100755
--- a/git-checkout.sh
+++ b/contrib/examples/git-checkout.sh
@@ -210,11 +210,14 @@ then
git read-tree $v --reset -u $new
else
git update-index --refresh >/dev/null
- merge_error=$(git read-tree -m -u --exclude-per-directory=.gitignore $old $new 2>&1) || (
- case "$merge" in
- '')
- echo >&2 "$merge_error"
+ git read-tree $v -m -u --exclude-per-directory=.gitignore $old $new || (
+ case "$merge,$v" in
+ ,*)
exit 1 ;;
+ 1,)
+ ;; # quiet
+ *)
+ echo >&2 "Falling back to 3-way merge..." ;;
esac
# Match the index to the working tree, and do a three-way.
diff --git a/copy.c b/copy.c
index c225d1b0f..afc4fbf41 100644
--- a/copy.c
+++ b/copy.c
@@ -34,3 +34,24 @@ int copy_fd(int ifd, int ofd)
close(ifd);
return 0;
}
+
+int copy_file(const char *dst, const char *src, int mode)
+{
+ int fdi, fdo, status;
+
+ mode = (mode & 0111) ? 0777 : 0666;
+ if ((fdi = open(src, O_RDONLY)) < 0)
+ return fdi;
+ if ((fdo = open(dst, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0) {
+ close(fdi);
+ return fdo;
+ }
+ status = copy_fd(fdi, fdo);
+ if (close(fdo) != 0)
+ return error("%s: write error: %s", dst, strerror(errno));
+
+ if (!status && adjust_shared_perm(dst))
+ return -1;
+
+ return status;
+}
diff --git a/date.c b/date.c
index 8f7050027..a74ed8642 100644
--- a/date.c
+++ b/date.c
@@ -213,9 +213,9 @@ static const struct {
{ "EAST", +10, 0, }, /* Eastern Australian Standard */
{ "EADT", +10, 1, }, /* Eastern Australian Daylight */
{ "GST", +10, 0, }, /* Guam Standard, USSR Zone 9 */
- { "NZT", +11, 0, }, /* New Zealand */
- { "NZST", +11, 0, }, /* New Zealand Standard */
- { "NZDT", +11, 1, }, /* New Zealand Daylight */
+ { "NZT", +12, 0, }, /* New Zealand */
+ { "NZST", +12, 0, }, /* New Zealand Standard */
+ { "NZDT", +12, 1, }, /* New Zealand Daylight */
{ "IDLE", +12, 0, }, /* International Date Line East */
};
diff --git a/diff-lib.c b/diff-lib.c
index 03eaa7cef..94b150e83 100644
--- a/diff-lib.c
+++ b/diff-lib.c
@@ -737,7 +737,8 @@ int run_diff_index(struct rev_info *revs, int cached)
opts.unpack_data = revs;
init_tree_desc(&t, tree->buffer, tree->size);
- unpack_trees(1, &t, &opts);
+ if (unpack_trees(1, &t, &opts))
+ exit(128);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
@@ -789,6 +790,7 @@ int do_diff_cache(const unsigned char *tree_sha1, struct diff_options *opt)
opts.unpack_data = &revs;
init_tree_desc(&t, tree->buffer, tree->size);
- unpack_trees(1, &t, &opts);
+ if (unpack_trees(1, &t, &opts))
+ exit(128);
return 0;
}
diff --git a/diff.c b/diff.c
index f08e663b8..ad1616423 100644
--- a/diff.c
+++ b/diff.c
@@ -271,8 +271,8 @@ static void print_line_count(int count)
}
}
-static void copy_file(int prefix, const char *data, int size,
- const char *set, const char *reset)
+static void copy_file_with_prefix(int prefix, const char *data, int size,
+ const char *set, const char *reset)
{
int ch, nl_just_seen = 1;
while (0 < size--) {
@@ -330,9 +330,9 @@ static void emit_rewrite_diff(const char *name_a,
print_line_count(lc_b);
printf(" @@%s\n", reset);
if (lc_a)
- copy_file('-', one->data, one->size, old, reset);
+ copy_file_with_prefix('-', one->data, one->size, old, reset);
if (lc_b)
- copy_file('+', two->data, two->size, new, reset);
+ copy_file_with_prefix('+', two->data, two->size, new, reset);
}
static int fill_mmfile(mmfile_t *mf, struct diff_filespec *one)
@@ -979,6 +979,90 @@ static void show_numstat(struct diffstat_t* data, struct diff_options *options)
}
}
+struct diffstat_dir {
+ struct diffstat_file **files;
+ int nr, percent, cumulative;
+};
+
+static long gather_dirstat(struct diffstat_dir *dir, unsigned long changed, const char *base, int baselen)
+{
+ unsigned long this_dir = 0;
+ unsigned int sources = 0;
+
+ while (dir->nr) {
+ struct diffstat_file *f = *dir->files;
+ int namelen = strlen(f->name);
+ unsigned long this;
+ char *slash;
+
+ if (namelen < baselen)
+ break;
+ if (memcmp(f->name, base, baselen))
+ break;
+ slash = strchr(f->name + baselen, '/');
+ if (slash) {
+ int newbaselen = slash + 1 - f->name;
+ this = gather_dirstat(dir, changed, f->name, newbaselen);
+ sources++;
+ } else {
+ if (f->is_unmerged || f->is_binary)
+ this = 0;
+ else
+ this = f->added + f->deleted;
+ dir->files++;
+ dir->nr--;
+ sources += 2;
+ }
+ this_dir += this;
+ }
+
+ /*
+ * We don't report dirstat's for
+ * - the top level
+ * - or cases where everything came from a single directory
+ * under this directory (sources == 1).
+ */
+ if (baselen && sources != 1) {
+ int permille = this_dir * 1000 / changed;
+ if (permille) {
+ int percent = permille / 10;
+ if (percent >= dir->percent) {
+ printf("%4d.%01d%% %.*s\n", percent, permille % 10, baselen, base);
+ if (!dir->cumulative)
+ return 0;
+ }
+ }
+ }
+ return this_dir;
+}
+
+static void show_dirstat(struct diffstat_t *data, struct diff_options *options)
+{
+ int i;
+ unsigned long changed;
+ struct diffstat_dir dir;
+
+ /* Calculate total changes */
+ changed = 0;
+ for (i = 0; i < data->nr; i++) {
+ if (data->files[i]->is_binary || data->files[i]->is_unmerged)
+ continue;
+ changed += data->files[i]->added;
+ changed += data->files[i]->deleted;
+ }
+
+ /* This can happen even with many files, if everything was renames */
+ if (!changed)
+ return;
+
+ /* Show all directories with more than x% of the changes */
+ dir.files = data->files;
+ dir.nr = data->nr;
+ dir.percent = options->dirstat_percent;
+ dir.cumulative = options->output_format & DIFF_FORMAT_CUMULATIVE;
+ gather_dirstat(&dir, changed, "", 0);
+}
+
static void free_diffstat_info(struct diffstat_t *diffstat)
{
int i;
@@ -1396,6 +1480,7 @@ static void builtin_diffstat(const char *name_a, const char *name_b,
}
static void builtin_checkdiff(const char *name_a, const char *name_b,
+ const char *attr_path,
struct diff_filespec *one,
struct diff_filespec *two, struct diff_options *o)
{
@@ -1410,7 +1495,7 @@ static void builtin_checkdiff(const char *name_a, const char *name_b,
data.filename = name_b ? name_b : name_a;
data.lineno = 0;
data.color_diff = DIFF_OPT_TST(o, COLOR_DIFF);
- data.ws_rule = whitespace_rule(data.filename);
+ data.ws_rule = whitespace_rule(attr_path);
if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
die("unable to read files to diff");
@@ -1835,6 +1920,9 @@ static const char *external_diff_attr(const char *name)
{
struct git_attr_check attr_diff_check;
+ if (!name)
+ return NULL;
+
setup_diff_attr_check(&attr_diff_check);
if (!git_checkattr(name, 1, &attr_diff_check)) {
const char *value = attr_diff_check.value;
@@ -1854,6 +1942,7 @@ static const char *external_diff_attr(const char *name)
static void run_diff_cmd(const char *pgm,
const char *name,
const char *other,
+ const char *attr_path,
struct diff_filespec *one,
struct diff_filespec *two,
const char *xfrm_msg,
@@ -1863,7 +1952,7 @@ static void run_diff_cmd(const char *pgm,
if (!DIFF_OPT_TST(o, ALLOW_EXTERNAL))
pgm = NULL;
else {
- const char *cmd = external_diff_attr(name);
+ const char *cmd = external_diff_attr(attr_path);
if (cmd)
pgm = cmd;
}
@@ -1904,6 +1993,15 @@ static int similarity_index(struct diff_filepair *p)
return p->score * 100 / MAX_SCORE;
}
+static void strip_prefix(int prefix_length, const char **namep, const char **otherp)
+{
+ /* Strip the prefix but do not molest /dev/null and absolute paths */
+ if (*namep && **namep != '/')
+ *namep += prefix_length;
+ if (*otherp && **otherp != '/')
+ *otherp += prefix_length;
+}
+
static void run_diff(struct diff_filepair *p, struct diff_options *o)
{
const char *pgm = external_diff();
@@ -1913,16 +2011,21 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
struct diff_filespec *two = p->two;
const char *name;
const char *other;
+ const char *attr_path;
int complete_rewrite = 0;
+ name = p->one->path;
+ other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+ attr_path = name;
+ if (o->prefix_length)
+ strip_prefix(o->prefix_length, &name, &other);
if (DIFF_PAIR_UNMERGED(p)) {
- run_diff_cmd(pgm, p->one->path, NULL, NULL, NULL, NULL, o, 0);
+ run_diff_cmd(pgm, name, NULL, attr_path,
+ NULL, NULL, NULL, o, 0);
return;
}
- name = p->one->path;
- other = (strcmp(name, p->two->path) ? p->two->path : NULL);
diff_fill_sha1_info(one);
diff_fill_sha1_info(two);
@@ -1985,15 +2088,17 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
* needs to be split into deletion and creation.
*/
struct diff_filespec *null = alloc_filespec(two->path);
- run_diff_cmd(NULL, name, other, one, null, xfrm_msg, o, 0);
+ run_diff_cmd(NULL, name, other, attr_path,
+ one, null, xfrm_msg, o, 0);
free(null);
null = alloc_filespec(one->path);
- run_diff_cmd(NULL, name, other, null, two, xfrm_msg, o, 0);
+ run_diff_cmd(NULL, name, other, attr_path,
+ null, two, xfrm_msg, o, 0);
free(null);
}
else
- run_diff_cmd(pgm, name, other, one, two, xfrm_msg, o,
- complete_rewrite);
+ run_diff_cmd(pgm, name, other, attr_path,
+ one, two, xfrm_msg, o, complete_rewrite);
strbuf_release(&msg);
}
@@ -2014,6 +2119,9 @@ static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
name = p->one->path;
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+ if (o->prefix_length)
+ strip_prefix(o->prefix_length, &name, &other);
+
diff_fill_sha1_info(p->one);
diff_fill_sha1_info(p->two);
@@ -2026,6 +2134,7 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
{
const char *name;
const char *other;
+ const char *attr_path;
if (DIFF_PAIR_UNMERGED(p)) {
/* unmerged */
@@ -2034,11 +2143,15 @@ static void run_checkdiff(struct diff_filepair *p, struct diff_options *o)
name = p->one->path;
other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+ attr_path = other ? other : name;
+
+ if (o->prefix_length)
+ strip_prefix(o->prefix_length, &name, &other);
diff_fill_sha1_info(p->one);
diff_fill_sha1_info(p->two);
- builtin_checkdiff(name, other, p->one, p->two, o);
+ builtin_checkdiff(name, other, attr_path, p->one, p->two, o);
}
void diff_setup(struct diff_options *options)
@@ -2047,6 +2160,7 @@ void diff_setup(struct diff_options *options)
options->line_termination = '\n';
options->break_opt = -1;
options->rename_limit = -1;
+ options->dirstat_percent = 3;
options->context = 3;
options->msg_sep = "";
@@ -2080,6 +2194,13 @@ int diff_setup_done(struct diff_options *options)
if (DIFF_OPT_TST(options, FIND_COPIES_HARDER))
options->detect_rename = DIFF_DETECT_COPY;
+ if (!DIFF_OPT_TST(options, RELATIVE_NAME))
+ options->prefix = NULL;
+ if (options->prefix)
+ options->prefix_length = strlen(options->prefix);
+ else
+ options->prefix_length = 0;
+
if (options->output_format & (DIFF_FORMAT_NAME |
DIFF_FORMAT_NAME_STATUS |
DIFF_FORMAT_CHECKDIFF |
@@ -2088,6 +2209,7 @@ int diff_setup_done(struct diff_options *options)
DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
DIFF_FORMAT_SHORTSTAT |
+ DIFF_FORMAT_DIRSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_PATCH);
@@ -2099,6 +2221,7 @@ int diff_setup_done(struct diff_options *options)
DIFF_FORMAT_NUMSTAT |
DIFF_FORMAT_DIFFSTAT |
DIFF_FORMAT_SHORTSTAT |
+ DIFF_FORMAT_DIRSTAT |
DIFF_FORMAT_SUMMARY |
DIFF_FORMAT_CHECKDIFF))
DIFF_OPT_SET(options, RECURSIVE);
@@ -2209,6 +2332,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
options->output_format |= DIFF_FORMAT_NUMSTAT;
else if (!strcmp(arg, "--shortstat"))
options->output_format |= DIFF_FORMAT_SHORTSTAT;
+ else if (opt_arg(arg, 'X', "dirstat", &options->dirstat_percent))
+ options->output_format |= DIFF_FORMAT_DIRSTAT;
+ else if (!strcmp(arg, "--cumulative"))
+ options->output_format |= DIFF_FORMAT_CUMULATIVE;
else if (!strcmp(arg, "--check"))
options->output_format |= DIFF_FORMAT_CHECKDIFF;
else if (!strcmp(arg, "--summary"))
@@ -2268,6 +2395,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
}
else if (!strcmp(arg, "--no-renames"))
options->detect_rename = 0;
+ else if (!strcmp(arg, "--relative"))
+ DIFF_OPT_SET(options, RELATIVE_NAME);
+ else if (!prefixcmp(arg, "--relative=")) {
+ DIFF_OPT_SET(options, RELATIVE_NAME);
+ options->prefix = arg + 11;
+ }
/* xdiff options */
else if (!strcmp(arg, "-w") || !strcmp(arg, "--ignore-all-space"))
@@ -2479,12 +2612,20 @@ static void diff_flush_raw(struct diff_filepair *p, struct diff_options *opt)
printf("%c%c", p->status, inter_name_termination);
}
- if (p->status == DIFF_STATUS_COPIED || p->status == DIFF_STATUS_RENAMED) {
- write_name_quoted(p->one->path, stdout, inter_name_termination);
- write_name_quoted(p->two->path, stdout, line_termination);
+ if (p->status == DIFF_STATUS_COPIED ||
+ p->status == DIFF_STATUS_RENAMED) {
+ const char *name_a, *name_b;
+ name_a = p->one->path;
+ name_b = p->two->path;
+ strip_prefix(opt->prefix_length, &name_a, &name_b);
+ write_name_quoted(name_a, stdout, inter_name_termination);
+ write_name_quoted(name_b, stdout, line_termination);
} else {
- const char *path = p->one->mode ? p->one->path : p->two->path;
- write_name_quoted(path, stdout, line_termination);
+ const char *name_a, *name_b;
+ name_a = p->one->mode ? p->one->path : p->two->path;
+ name_b = NULL;
+ strip_prefix(opt->prefix_length, &name_a, &name_b);
+ write_name_quoted(name_a, stdout, line_termination);
}
}
@@ -2681,8 +2822,13 @@ static void flush_one_pair(struct diff_filepair *p, struct diff_options *opt)
diff_flush_checkdiff(p, opt);
else if (fmt & (DIFF_FORMAT_RAW | DIFF_FORMAT_NAME_STATUS))
diff_flush_raw(p, opt);
- else if (fmt & DIFF_FORMAT_NAME)
- write_name_quoted(p->two->path, stdout, opt->line_termination);
+ else if (fmt & DIFF_FORMAT_NAME) {
+ const char *name_a, *name_b;
+ name_a = p->two->path;
+ name_b = NULL;
+ strip_prefix(opt->prefix_length, &name_a, &name_b);
+ write_name_quoted(name_a, stdout, opt->line_termination);
+ }
}
static void show_file_mode_name(const char *newdelete, struct diff_filespec *fs)
@@ -2927,7 +3073,7 @@ void diff_flush(struct diff_options *options)
separator++;
}
- if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT)) {
+ if (output_format & (DIFF_FORMAT_DIFFSTAT|DIFF_FORMAT_SHORTSTAT|DIFF_FORMAT_NUMSTAT|DIFF_FORMAT_DIRSTAT)) {
struct diffstat_t diffstat;
memset(&diffstat, 0, sizeof(struct diffstat_t));
@@ -2937,6 +3083,8 @@ void diff_flush(struct diff_options *options)
if (check_pair_status(p))
diff_flush_stat(p, options, &diffstat);
}
+ if (output_format & DIFF_FORMAT_DIRSTAT)
+ show_dirstat(&diffstat, options);
if (output_format & DIFF_FORMAT_NUMSTAT)
show_numstat(&diffstat, options);
if (output_format & DIFF_FORMAT_DIFFSTAT)
@@ -3168,6 +3316,11 @@ void diff_addremove(struct diff_options *options,
if (!path) path = "";
sprintf(concatpath, "%s%s", base, path);
+
+ if (options->prefix &&
+ strncmp(concatpath, options->prefix, options->prefix_length))
+ return;
+
one = alloc_filespec(concatpath);
two = alloc_filespec(concatpath);
@@ -3197,6 +3350,11 @@ void diff_change(struct diff_options *options,
}
if (!path) path = "";
sprintf(concatpath, "%s%s", base, path);
+
+ if (options->prefix &&
+ strncmp(concatpath, options->prefix, options->prefix_length))
+ return;
+
one = alloc_filespec(concatpath);
two = alloc_filespec(concatpath);
fill_filespec(one, old_sha1, old_mode);
@@ -3211,6 +3369,11 @@ void diff_unmerge(struct diff_options *options,
unsigned mode, const unsigned char *sha1)
{
struct diff_filespec *one, *two;
+
+ if (options->prefix &&
+ strncmp(path, options->prefix, options->prefix_length))
+ return;
+
one = alloc_filespec(path);
two = alloc_filespec(path);
fill_filespec(one, sha1, mode);
diff --git a/diff.h b/diff.h
index 8e73f07d7..9a652e7f3 100644
--- a/diff.h
+++ b/diff.h
@@ -30,6 +30,8 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_FORMAT_SUMMARY 0x0008
#define DIFF_FORMAT_PATCH 0x0010
#define DIFF_FORMAT_SHORTSTAT 0x0020
+#define DIFF_FORMAT_DIRSTAT 0x0040
+#define DIFF_FORMAT_CUMULATIVE 0x0080
/* These override all above */
#define DIFF_FORMAT_NAME 0x0100
@@ -60,6 +62,7 @@ typedef void (*diff_format_fn_t)(struct diff_queue_struct *q,
#define DIFF_OPT_EXIT_WITH_STATUS (1 << 14)
#define DIFF_OPT_REVERSE_DIFF (1 << 15)
#define DIFF_OPT_CHECK_FAILED (1 << 16)
+#define DIFF_OPT_RELATIVE_NAME (1 << 17)
#define DIFF_OPT_TST(opts, flag) ((opts)->flags & DIFF_OPT_##flag)
#define DIFF_OPT_SET(opts, flag) ((opts)->flags |= DIFF_OPT_##flag)
#define DIFF_OPT_CLR(opts, flag) ((opts)->flags &= ~DIFF_OPT_##flag)
@@ -80,8 +83,11 @@ struct diff_options {
int pickaxe_opts;
int rename_score;
int rename_limit;
+ int dirstat_percent;
int setup;
int abbrev;
+ const char *prefix;
+ int prefix_length;
const char *msg_sep;
const char *stat_sep;
long xdl_opts;
diff --git a/environment.c b/environment.c
index 3527f1663..6739a3f41 100644
--- a/environment.c
+++ b/environment.c
@@ -37,6 +37,7 @@ const char *excludes_file;
int auto_crlf = 0; /* 1: both ways, -1: only when adding git objects */
enum safe_crlf safe_crlf = SAFE_CRLF_WARN;
unsigned whitespace_rule_cfg = WS_DEFAULT_RULE;
+enum branch_track git_branch_track = BRANCH_TRACK_REMOTE;
/* This is set by setup_git_dir_gently() and/or git_default_config() */
char *git_work_tree_cfg;
diff --git a/git-bisect.sh b/git-bisect.sh
index 74715edf0..2c32d0b9e 100755
--- a/git-bisect.sh
+++ b/git-bisect.sh
@@ -67,16 +67,18 @@ bisect_start() {
die "Bad HEAD - I need a HEAD"
case "$head" in
refs/heads/bisect)
- if [ -s "$GIT_DIR/head-name" ]; then
- branch=`cat "$GIT_DIR/head-name"`
+ if [ -s "$GIT_DIR/BISECT_START" ]; then
+ branch=`cat "$GIT_DIR/BISECT_START"`
else
branch=master
fi
git checkout $branch || exit
;;
refs/heads/*|$_x40)
+ # This error message should only be triggered by cogito usage,
+ # and cogito users should understand it relates to cg-seek.
[ -s "$GIT_DIR/head-name" ] && die "won't bisect on seeked tree"
- echo "${head#refs/heads/}" >"$GIT_DIR/head-name"
+ echo "${head#refs/heads/}" >"$GIT_DIR/BISECT_START"
;;
*)
die "Bad HEAD - strange symbolic ref"
@@ -353,8 +355,8 @@ bisect_reset() {
return
}
case "$#" in
- 0) if [ -s "$GIT_DIR/head-name" ]; then
- branch=`cat "$GIT_DIR/head-name"`
+ 0) if [ -s "$GIT_DIR/BISECT_START" ]; then
+ branch=`cat "$GIT_DIR/BISECT_START"`
else
branch=master
fi ;;
@@ -365,7 +367,9 @@ bisect_reset() {
usage ;;
esac
if git checkout "$branch"; then
+ # Cleanup head-name if it got left by an old version of git-bisect
rm -f "$GIT_DIR/head-name"
+ rm -f "$GIT_DIR/BISECT_START"
bisect_clean_state
fi
}
diff --git a/git-gui/Makefile b/git-gui/Makefile
index 081d7550a..01e0a46ba 100644
--- a/git-gui/Makefile
+++ b/git-gui/Makefile
@@ -92,8 +92,12 @@ ifndef V
REMOVE_F1 = && echo ' ' REMOVE `basename "$$dst"` && $(RM_RF) "$$dst"
endif
-TCL_PATH ?= tclsh
TCLTK_PATH ?= wish
+ifeq (./,$(dir $(TCLTK_PATH)))
+ TCL_PATH ?= $(subst wish,tclsh,$(TCLTK_PATH))
+else
+ TCL_PATH ?= $(dir $(TCLTK_PATH))$(notdir $(subst wish,tclsh,$(TCLTK_PATH)))
+endif
ifeq ($(uname_S),Darwin)
TKFRAMEWORK = /Library/Frameworks/Tk.framework/Resources/Wish.app
@@ -127,7 +131,17 @@ GITGUI_MACOSXAPP :=
ifeq ($(uname_O),Cygwin)
GITGUI_SCRIPT := `cygpath --windows --absolute "$(GITGUI_SCRIPT)"`
- gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)")
+
+ # Is this a Cygwin Tcl/Tk binary? If so it knows how to do
+ # POSIX path translation just like cygpath does and we must
+ # keep libdir in POSIX format so Cygwin packages of git-gui
+ # work no matter where the user installs them.
+ #
+ ifeq ($(shell echo 'puts [file normalize /]' | '$(TCL_PATH_SQ)'),$(shell cygpath --mixed --absolute /))
+ gg_libdir_sed_in := $(gg_libdir)
+ else
+ gg_libdir_sed_in := $(shell cygpath --windows --absolute "$(gg_libdir)")
+ endif
else
ifeq ($(exedir),$(gg_libdir))
GITGUI_RELATIVE := 1
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 5d65272e2..238a2393f 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -663,7 +663,7 @@ if {![regsub {^git version } $_git_version {} _git_version]} {
}
set _real_git_version $_git_version
-regsub -- {-dirty$} $_git_version {} _git_version
+regsub -- {[\-\.]dirty$} $_git_version {} _git_version
regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version
regsub {\.rc[0-9]+$} $_git_version {} _git_version
regsub {\.GIT$} $_git_version {} _git_version
diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
index 86faf24cc..0adcf9d95 100644
--- a/git-gui/lib/choose_repository.tcl
+++ b/git-gui/lib/choose_repository.tcl
@@ -11,6 +11,7 @@ field w_quit ; # Quit button
field o_cons ; # Console object (if active)
field w_types ; # List of type buttons in clone
field w_recentlist ; # Listbox containing recent repositories
+field w_localpath ; # Entry widget bound to local_path
field done 0 ; # Finished picking the repository?
field local_path {} ; # Where this repository is locally
@@ -385,6 +386,7 @@ method _do_new {} {
button $w_body.where.b \
-text [mc "Browse"] \
-command [cb _new_local_path]
+ set w_localpath $w_body.where.t
pack $w_body.where.b -side right
pack $w_body.where.l -side left
@@ -416,6 +418,7 @@ method _new_local_path {} {
return
}
set local_path $p
+ $w_localpath icursor end
}
method _do_new2 {} {
@@ -481,6 +484,7 @@ method _do_clone {} {
-text [mc "Browse"] \
-command [cb _new_local_path]
grid $args.where_l $args.where_t $args.where_b -sticky ew
+ set w_localpath $args.where_t
label $args.type_l -text [mc "Clone Type:"]
frame $args.type_f
diff --git a/git-gui/lib/error.tcl b/git-gui/lib/error.tcl
index 0fdd7531d..08a24622c 100644
--- a/git-gui/lib/error.tcl
+++ b/git-gui/lib/error.tcl
@@ -1,6 +1,14 @@
# git-gui branch (create/delete) support
# Copyright (C) 2006, 2007 Shawn Pearce
+proc _error_parent {} {
+ set p [grab current .]
+ if {$p eq {}} {
+ return .
+ }
+ return $p
+}
+
proc error_popup {msg} {
set title [appname]
if {[reponame] ne {}} {
@@ -11,8 +19,8 @@ proc error_popup {msg} {
-type ok \
-title [append "$title: " [mc "error"]] \
-message $msg]
- if {[winfo ismapped .]} {
- lappend cmd -parent .
+ if {[winfo ismapped [_error_parent]]} {
+ lappend cmd -parent [_error_parent]
}
eval $cmd
}
@@ -27,13 +35,13 @@ proc warn_popup {msg} {
-type ok \
-title [append "$title: " [mc "warning"]] \
-message $msg]
- if {[winfo ismapped .]} {
- lappend cmd -parent .
+ if {[winfo ismapped [_error_parent]]} {
+ lappend cmd -parent [_error_parent]
}
eval $cmd
}
-proc info_popup {msg {parent .}} {
+proc info_popup {msg} {
set title [appname]
if {[reponame] ne {}} {
append title " ([reponame])"
@@ -56,8 +64,8 @@ proc ask_popup {msg} {
-type yesno \
-title $title \
-message $msg]
- if {[winfo ismapped .]} {
- lappend cmd -parent .
+ if {[winfo ismapped [_error_parent]]} {
+ lappend cmd -parent [_error_parent]
}
eval $cmd
}
diff --git a/git-pull.sh b/git-pull.sh
index 46da0f4ca..3ce32b5f2 100755
--- a/git-pull.sh
+++ b/git-pull.sh
@@ -174,6 +174,7 @@ fi
merge_name=$(git fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit
test true = "$rebase" &&
- exec git-rebase --onto $merge_head ${oldremoteref:-$merge_head}
+ exec git-rebase $strategy_args --onto $merge_head \
+ ${oldremoteref:-$merge_head}
exec git-merge $no_summary $no_commit $squash $no_ff $strategy_args \
"$merge_name" HEAD $merge_head
diff --git a/git.c b/git.c
index 0cb86884d..9cca81a60 100644
--- a/git.c
+++ b/git.c
@@ -87,19 +87,6 @@ static int handle_options(const char*** argv, int* argc, int* envchanged)
return handled;
}
-static const char *alias_command;
-static char *alias_string;
-
-static int git_alias_config(const char *var, const char *value)
-{
- if (!prefixcmp(var, "alias.") && !strcmp(var + 6, alias_command)) {
- if (!value)
- return config_error_nonbool(var);
- alias_string = xstrdup(value);
- }
- return 0;
-}
-
static int split_cmdline(char *cmdline, const char ***argv)
{
int src, dst, count = 0, size = 16;
@@ -159,11 +146,13 @@ static int handle_alias(int *argcp, const char ***argv)
const char *subdir;
int count, option_count;
const char** new_argv;
+ const char *alias_command;
+ char *alias_string;
subdir = setup_git_directory_gently(&nongit);
alias_command = (*argv)[0];
- git_config(git_alias_config);
+ alias_string = alias_lookup(alias_command);
if (alias_string) {
if (alias_string[0] == '!') {
if (*argcp > 1) {
@@ -289,6 +278,7 @@ static void handle_internal_command(int argc, const char **argv)
{ "branch", cmd_branch, RUN_SETUP },
{ "bundle", cmd_bundle },
{ "cat-file", cmd_cat_file, RUN_SETUP },
+ { "checkout", cmd_checkout, RUN_SETUP | NEED_WORK_TREE },
{ "checkout-index", cmd_checkout_index,
RUN_SETUP | NEED_WORK_TREE},
{ "check-ref-format", cmd_check_ref_format },
@@ -332,6 +322,8 @@ static void handle_internal_command(int argc, const char **argv)
{ "merge-base", cmd_merge_base, RUN_SETUP },
{ "merge-file", cmd_merge_file },
{ "merge-ours", cmd_merge_ours, RUN_SETUP },
+ { "merge-recursive", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
+ { "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE },
{ "mv", cmd_mv, RUN_SETUP | NEED_WORK_TREE },
{ "name-rev", cmd_name_rev, RUN_SETUP },
{ "pack-objects", cmd_pack_objects, RUN_SETUP },
diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl
index 326e27cf8..fc95e2ca8 100755
--- a/gitweb/gitweb.perl
+++ b/gitweb/gitweb.perl
@@ -848,32 +848,73 @@ sub project_in_list {
## ----------------------------------------------------------------------
## HTML aware string manipulation
+# Try to chop given string on a word boundary between position
+# $len and $len+$add_len. If there is no word boundary there,
+# chop at $len+$add_len. Do not chop if chopped part plus ellipsis
+# (marking chopped part) would be longer than given string.
sub chop_str {
my $str = shift;
my $len = shift;
my $add_len = shift || 10;
+ my $where = shift || 'right'; # 'left' | 'center' | 'right'
# allow only $len chars, but don't cut a word if it would fit in $add_len
# if it doesn't fit, cut it if it's still longer than the dots we would add
- $str =~ m/^(.{0,$len}[^ \/\-_:\.@]{0,$add_len})(.*)/;
- my $body = $1;
- my $tail = $2;
- if (length($tail) > 4) {
- $tail = " ...";
- $body =~ s/&[^;]*$//; # remove chopped character entities
+ # remove chopped character entities entirely
+
+ # when chopping in the middle, distribute $len into left and right part
+ # return early if chopping wouldn't make string shorter
+ if ($where eq 'center') {
+ return $str if ($len + 5 >= length($str)); # filler is length 5
+ $len = int($len/2);
+ } else {
+ return $str if ($len + 4 >= length($str)); # filler is length 4
+ }
+
+ # regexps: ending and beginning with word part up to $add_len
+ my $endre = qr/.{$len}\w{0,$add_len}/;
+ my $begre = qr/\w{0,$add_len}.{$len}/;
+
+ if ($where eq 'left') {
+ $str =~ m/^(.*?)($begre)$/;
+ my ($lead, $body) = ($1, $2);
+ if (length($lead) > 4) {
+ $body =~ s/^[^;]*;// if ($lead =~ m/&[^;]*$/);
+ $lead = " ...";
+ }
+ return "$lead$body";
+
+ } elsif ($where eq 'center') {
+ $str =~ m/^($endre)(.*)$/;
+ my ($left, $str) = ($1, $2);
+ $str =~ m/^(.*?)($begre)$/;
+ my ($mid, $right) = ($1, $2);
+ if (length($mid) > 5) {
+ $left =~ s/&[^;]*$//;
+ $right =~ s/^[^;]*;// if ($mid =~ m/&[^;]*$/);
+ $mid = " ... ";
+ }
+ return "$left$mid$right";
+
+ } else {
+ $str =~ m/^($endre)(.*)$/;
+ my $body = $1;
+ my $tail = $2;
+ if (length($tail) > 4) {
+ $body =~ s/&[^;]*$//;
+ $tail = "... ";
+ }
+ return "$body$tail";
}
- return "$body$tail";
}
# takes the same arguments as chop_str, but also wraps a <span> around the
# result with a title attribute if it does get chopped. Additionally, the
# string is HTML-escaped.
sub chop_and_escape_str {
- my $str = shift;
- my $len = shift;
- my $add_len = shift || 10;
+ my ($str) = @_;
- my $chopped = chop_str($str, $len, $add_len);
+ my $chopped = chop_str(@_);
if ($chopped eq $str) {
return esc_html($chopped);
} else {
@@ -3791,11 +3832,11 @@ sub git_search_grep_body {
foreach my $line (@$comment) {
if ($line =~ m/^(.*)($search_regexp)(.*)$/i) {
my ($lead, $match, $trail) = ($1, $2, $3);
- $match = chop_str($match, 70, 5); # in case match is very long
- my $contextlen = (80 - len($match))/2; # is left for the remainder
- $contextlen = 30 if ($contextlen > 30); # but not too much
- $lead = chop_str($lead, $contextlen, 10);
- $trail = chop_str($trail, $contextlen, 10);
+ $match = chop_str($match, 70, 5, 'center');
+ my $contextlen = int((80 - length($match))/2);
+ $contextlen = 30 if ($contextlen > 30);
+ $lead = chop_str($lead, $contextlen, 10, 'left');
+ $trail = chop_str($trail, $contextlen, 10, 'right');
$lead = esc_html($lead);
$match = esc_html($match);
diff --git a/hash-object.c b/hash-object.c
index 0a58f3f12..61e7160b3 100644
--- a/hash-object.c
+++ b/hash-object.c
@@ -41,6 +41,7 @@ int main(int argc, char **argv)
const char *prefix = NULL;
int prefix_length = -1;
int no_more_flags = 0;
+ int hashstdin = 0;
git_config(git_default_config);
@@ -65,13 +66,20 @@ int main(int argc, char **argv)
else if (!strcmp(argv[i], "--help"))
usage(hash_object_usage);
else if (!strcmp(argv[i], "--stdin")) {
- hash_stdin(type, write_object);
+ if (hashstdin)
+ die("Multiple --stdin arguments are not supported");
+ hashstdin = 1;
}
else
usage(hash_object_usage);
}
else {
const char *arg = argv[i];
+
+ if (hashstdin) {
+ hash_stdin(type, write_object);
+ hashstdin = 0;
+ }
if (0 <= prefix_length)
arg = prefix_filename(prefix, prefix_length,
arg);
@@ -79,5 +87,7 @@ int main(int argc, char **argv)
no_more_flags = 1;
}
}
+ if (hashstdin)
+ hash_stdin(type, write_object);
return 0;
}
diff --git a/help.c b/help.c
index 8143dcdd3..e57a50ed5 100644
--- a/help.c
+++ b/help.c
@@ -7,40 +7,49 @@
#include "builtin.h"
#include "exec_cmd.h"
#include "common-cmds.h"
-
-static const char *help_default_format;
-
-static enum help_format {
- man_format,
- info_format,
- web_format,
-} help_format = man_format;
-
-static void parse_help_format(const char *format)
+#include "parse-options.h"
+
+enum help_format {
+ HELP_FORMAT_MAN,
+ HELP_FORMAT_INFO,
+ HELP_FORMAT_WEB,
+};
+
+static int show_all = 0;
+static enum help_format help_format = HELP_FORMAT_MAN;
+static struct option builtin_help_options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+ OPT_SET_INT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+ OPT_SET_INT('w', "web", &help_format, "show manual in web browser",
+ HELP_FORMAT_WEB),
+ OPT_SET_INT('i', "info", &help_format, "show info page",
+ HELP_FORMAT_INFO),
+};
+
+static const char * const builtin_help_usage[] = {
+ "git-help [--all] [--man|--web|--info] [command]",
+ NULL
+};
+
+static enum help_format parse_help_format(const char *format)
{
- if (!format) {
- help_format = man_format;
- return;
- }
- if (!strcmp(format, "man")) {
- help_format = man_format;
- return;
- }
- if (!strcmp(format, "info")) {
- help_format = info_format;
- return;
- }
- if (!strcmp(format, "web") || !strcmp(format, "html")) {
- help_format = web_format;
- return;
- }
+ if (!strcmp(format, "man"))
+ return HELP_FORMAT_MAN;
+ if (!strcmp(format, "info"))
+ return HELP_FORMAT_INFO;
+ if (!strcmp(format, "web") || !strcmp(format, "html"))
+ return HELP_FORMAT_WEB;
die("unrecognized help format '%s'", format);
}
static int git_help_config(const char *var, const char *value)
{
- if (!strcmp(var, "help.format"))
- return git_config_string(&help_default_format, var, value);
+ if (!strcmp(var, "help.format")) {
+ if (!value)
+ return config_error_nonbool(var);
+ help_format = parse_help_format(value);
+ return 0;
+ }
return git_default_config(var, value);
}
@@ -201,7 +210,7 @@ static unsigned int list_commands_in_dir(struct cmdnames *cmds,
return longest;
}
-static void list_commands(void)
+static unsigned int load_command_list(void)
{
unsigned int longest = 0;
unsigned int len;
@@ -241,6 +250,14 @@ static void list_commands(void)
uniq(&other_cmds);
exclude_cmds(&other_cmds, &main_cmds);
+ return longest;
+}
+
+static void list_commands(void)
+{
+ unsigned int longest = load_command_list();
+ const char *exec_path = git_exec_path();
+
if (main_cmds.cnt) {
printf("available git commands in '%s'\n", exec_path);
printf("----------------------------");
@@ -275,6 +292,22 @@ void list_common_cmds_help(void)
}
}
+static int is_in_cmdlist(struct cmdnames *c, const char *s)
+{
+ int i;
+ for (i = 0; i < c->cnt; i++)
+ if (!strcmp(s, c->names[i]->name))
+ return 1;
+ return 0;
+}
+
+static int is_git_command(const char *s)
+{
+ load_command_list();
+ return is_in_cmdlist(&main_cmds, s) ||
+ is_in_cmdlist(&other_cmds, s);
+}
+
static const char *cmd_to_page(const char *git_cmd)
{
if (!git_cmd)
@@ -362,50 +395,43 @@ int cmd_version(int argc, const char **argv, const char *prefix)
int cmd_help(int argc, const char **argv, const char *prefix)
{
- const char *help_cmd = argv[1];
+ int nongit;
+ const char *alias;
- if (argc < 2) {
- printf("usage: %s\n\n", git_usage_string);
- list_common_cmds_help();
- exit(0);
- }
+ setup_git_directory_gently(&nongit);
+ git_config(git_help_config);
+
+ argc = parse_options(argc, argv, builtin_help_options,
+ builtin_help_usage, 0);
- if (!strcmp(help_cmd, "--all") || !strcmp(help_cmd, "-a")) {
+ if (show_all) {
printf("usage: %s\n\n", git_usage_string);
list_commands();
+ return 0;
}
- else if (!strcmp(help_cmd, "--web") || !strcmp(help_cmd, "-w")) {
- show_html_page(argc > 2 ? argv[2] : NULL);
- }
-
- else if (!strcmp(help_cmd, "--info") || !strcmp(help_cmd, "-i")) {
- show_info_page(argc > 2 ? argv[2] : NULL);
+ if (!argv[0]) {
+ printf("usage: %s\n\n", git_usage_string);
+ list_common_cmds_help();
+ return 0;
}
- else if (!strcmp(help_cmd, "--man") || !strcmp(help_cmd, "-m")) {
- show_man_page(argc > 2 ? argv[2] : NULL);
+ alias = alias_lookup(argv[0]);
+ if (alias && !is_git_command(argv[0])) {
+ printf("`git %s' is aliased to `%s'\n", argv[0], alias);
+ return 0;
}
- else {
- int nongit;
-
- setup_git_directory_gently(&nongit);
- git_config(git_help_config);
- if (help_default_format)
- parse_help_format(help_default_format);
-
- switch (help_format) {
- case man_format:
- show_man_page(help_cmd);
- break;
- case info_format:
- show_info_page(help_cmd);
- break;
- case web_format:
- show_html_page(help_cmd);
- break;
- }
+ switch (help_format) {
+ case HELP_FORMAT_MAN:
+ show_man_page(argv[0]);
+ break;
+ case HELP_FORMAT_INFO:
+ show_info_page(argv[0]);
+ break;
+ case HELP_FORMAT_WEB:
+ show_html_page(argv[0]);
+ break;
}
return 0;
diff --git a/log-tree.c b/log-tree.c
index e9ba6df9d..608f697cf 100644
--- a/log-tree.c
+++ b/log-tree.c
@@ -137,6 +137,72 @@ static int has_non_ascii(const char *s)
return 0;
}
+void log_write_email_headers(struct rev_info *opt, const char *name,
+ const char **subject_p, const char **extra_headers_p)
+{
+ const char *subject = NULL;
+ const char *extra_headers = opt->extra_headers;
+ if (opt->total > 0) {
+ static char buffer[64];
+ snprintf(buffer, sizeof(buffer),
+ "Subject: [%s %0*d/%d] ",
+ opt->subject_prefix,
+ digits_in_number(opt->total),
+ opt->nr, opt->total);
+ subject = buffer;
+ } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
+ static char buffer[256];
+ snprintf(buffer, sizeof(buffer),
+ "Subject: [%s] ",
+ opt->subject_prefix);
+ subject = buffer;
+ } else {
+ subject = "Subject: ";
+ }
+
+ printf("From %s Mon Sep 17 00:00:00 2001\n", name);
+ if (opt->message_id)
+ printf("Message-Id: <%s>\n", opt->message_id);
+ if (opt->ref_message_id)
+ printf("In-Reply-To: <%s>\nReferences: <%s>\n",
+ opt->ref_message_id, opt->ref_message_id);
+ if (opt->mime_boundary) {
+ static char subject_buffer[1024];
+ static char buffer[1024];
+ snprintf(subject_buffer, sizeof(subject_buffer) - 1,
+ "%s"
+ "MIME-Version: 1.0\n"
+ "Content-Type: multipart/mixed;"
+ " boundary=\"%s%s\"\n"
+ "\n"
+ "This is a multi-part message in MIME "
+ "format.\n"
+ "--%s%s\n"
+ "Content-Type: text/plain; "
+ "charset=UTF-8; format=fixed\n"
+ "Content-Transfer-Encoding: 8bit\n\n",
+ extra_headers ? extra_headers : "",
+ mime_boundary_leader, opt->mime_boundary,
+ mime_boundary_leader, opt->mime_boundary);
+ extra_headers = subject_buffer;
+
+ snprintf(buffer, sizeof(buffer) - 1,
+ "--%s%s\n"
+ "Content-Type: text/x-patch;"
+ " name=\"%s.diff\"\n"
+ "Content-Transfer-Encoding: 8bit\n"
+ "Content-Disposition: %s;"
+ " filename=\"%s.diff\"\n\n",
+ mime_boundary_leader, opt->mime_boundary,
+ name,
+ opt->no_inline ? "attachment" : "inline",
+ name);
+ opt->diffopt.stat_sep = buffer;
+ }
+ *subject_p = subject;
+ *extra_headers_p = extra_headers;
+}
+
void show_log(struct rev_info *opt, const char *sep)
{
struct strbuf msgbuf;
@@ -188,64 +254,8 @@ void show_log(struct rev_info *opt, const char *sep)
*/
if (opt->commit_format == CMIT_FMT_EMAIL) {
- char *sha1 = sha1_to_hex(commit->object.sha1);
- if (opt->total > 0) {
- static char buffer[64];
- snprintf(buffer, sizeof(buffer),
- "Subject: [%s %0*d/%d] ",
- opt->subject_prefix,
- digits_in_number(opt->total),
- opt->nr, opt->total);
- subject = buffer;
- } else if (opt->total == 0 && opt->subject_prefix && *opt->subject_prefix) {
- static char buffer[256];
- snprintf(buffer, sizeof(buffer),
- "Subject: [%s] ",
- opt->subject_prefix);
- subject = buffer;
- } else {
- subject = "Subject: ";
- }
-
- printf("From %s Mon Sep 17 00:00:00 2001\n", sha1);
- if (opt->message_id)
- printf("Message-Id: <%s>\n", opt->message_id);
- if (opt->ref_message_id)
- printf("In-Reply-To: <%s>\nReferences: <%s>\n",
- opt->ref_message_id, opt->ref_message_id);
- if (opt->mime_boundary) {
- static char subject_buffer[1024];
- static char buffer[1024];
- snprintf(subject_buffer, sizeof(subject_buffer) - 1,
- "%s"
- "MIME-Version: 1.0\n"
- "Content-Type: multipart/mixed;"
- " boundary=\"%s%s\"\n"
- "\n"
- "This is a multi-part message in MIME "
- "format.\n"
- "--%s%s\n"
- "Content-Type: text/plain; "
- "charset=UTF-8; format=fixed\n"
- "Content-Transfer-Encoding: 8bit\n\n",
- extra_headers ? extra_headers : "",
- mime_boundary_leader, opt->mime_boundary,
- mime_boundary_leader, opt->mime_boundary);
- extra_headers = subject_buffer;
-
- snprintf(buffer, sizeof(buffer) - 1,
- "--%s%s\n"
- "Content-Type: text/x-patch;"
- " name=\"%s.diff\"\n"
- "Content-Transfer-Encoding: 8bit\n"
- "Content-Disposition: %s;"
- " filename=\"%s.diff\"\n\n",
- mime_boundary_leader, opt->mime_boundary,
- sha1,
- opt->no_inline ? "attachment" : "inline",
- sha1);
- opt->diffopt.stat_sep = buffer;
- }
+ log_write_email_headers(opt, sha1_to_hex(commit->object.sha1),
+ &subject, &extra_headers);
} else if (opt->commit_format != CMIT_FMT_USERFORMAT) {
fputs(diff_get_color_opt(&opt->diffopt, DIFF_COMMIT), stdout);
if (opt->commit_format != CMIT_FMT_ONELINE)
diff --git a/log-tree.h b/log-tree.h
index b33f7cd7a..0cc9344ea 100644
--- a/log-tree.h
+++ b/log-tree.h
@@ -13,5 +13,7 @@ int log_tree_commit(struct rev_info *, struct commit *);
int log_tree_opt_parse(struct rev_info *, const char **, int);
void show_log(struct rev_info *opt, const char *sep);
void show_decorations(struct commit *commit);
+void log_write_email_headers(struct rev_info *opt, const char *name,
+ const char **subject_p, const char **extra_headers_p);
#endif
diff --git a/merge-recursive.h b/merge-recursive.h
new file mode 100644
index 000000000..f37630a8a
--- /dev/null
+++ b/merge-recursive.h
@@ -0,0 +1,20 @@
+#ifndef MERGE_RECURSIVE_H
+#define MERGE_RECURSIVE_H
+
+int merge_recursive(struct commit *h1,
+ struct commit *h2,
+ const char *branch1,
+ const char *branch2,
+ struct commit_list *ancestors,
+ struct commit **result);
+
+int merge_trees(struct tree *head,
+ struct tree *merge,
+ struct tree *common,
+ const char *branch1,
+ const char *branch2,
+ struct tree **result);
+
+struct tree *write_tree_from_memory(void);
+
+#endif
diff --git a/pretty.c b/pretty.c
index 4bf3be2dd..703f52176 100644
--- a/pretty.c
+++ b/pretty.c
@@ -109,9 +109,9 @@ needquote:
strbuf_addstr(sb, "?=");
}
-static void add_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
- const char *line, enum date_mode dmode,
- const char *encoding)
+void pp_user_info(const char *what, enum cmit_fmt fmt, struct strbuf *sb,
+ const char *line, enum date_mode dmode,
+ const char *encoding)
{
char *date;
int namelen;
@@ -620,23 +620,23 @@ static void pp_header(enum cmit_fmt fmt,
*/
if (!memcmp(line, "author ", 7)) {
strbuf_grow(sb, linelen + 80);
- add_user_info("Author", fmt, sb, line + 7, dmode, encoding);
+ pp_user_info("Author", fmt, sb, line + 7, dmode, encoding);
}
if (!memcmp(line, "committer ", 10) &&
(fmt == CMIT_FMT_FULL || fmt == CMIT_FMT_FULLER)) {
strbuf_grow(sb, linelen + 80);
- add_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
+ pp_user_info("Commit", fmt, sb, line + 10, dmode, encoding);
}
}
}
-static void pp_title_line(enum cmit_fmt fmt,
- const char **msg_p,
- struct strbuf *sb,
- const char *subject,
- const char *after_subject,
- const char *encoding,
- int plain_non_ascii)
+void pp_title_line(enum cmit_fmt fmt,
+ const char **msg_p,
+ struct strbuf *sb,
+ const char *subject,
+ const char *after_subject,
+ const char *encoding,
+ int plain_non_ascii)
{
struct strbuf title;
@@ -685,10 +685,10 @@ static void pp_title_line(enum cmit_fmt fmt,
strbuf_release(&title);
}
-static void pp_remainder(enum cmit_fmt fmt,
- const char **msg_p,
- struct strbuf *sb,
- int indent)
+void pp_remainder(enum cmit_fmt fmt,
+ const char **msg_p,
+ struct strbuf *sb,
+ int indent)
{
int first = 1;
for (;;) {
diff --git a/read-cache.c b/read-cache.c
index e45f4b3d6..657f0c589 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -37,8 +37,13 @@ static unsigned int hash_name(const char *name, int namelen)
static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
{
void **pos;
- unsigned int hash = hash_name(ce->name, ce_namelen(ce));
+ unsigned int hash;
+ if (ce->ce_flags & CE_HASHED)
+ return;
+ ce->ce_flags |= CE_HASHED;
+ ce->next = NULL;
+ hash = hash_name(ce->name, ce_namelen(ce));
pos = insert_hash(hash, ce, &istate->name_hash);
if (pos) {
ce->next = *pos;
@@ -59,33 +64,18 @@ static void lazy_init_name_hash(struct index_state *istate)
static void set_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
{
+ ce->ce_flags &= ~CE_UNHASHED;
istate->cache[nr] = ce;
if (istate->name_hash_initialized)
hash_index_entry(istate, ce);
}
-/*
- * We don't actually *remove* it, we can just mark it invalid so that
- * we won't find it in lookups.
- *
- * Not only would we have to search the lists (simple enough), but
- * we'd also have to rehash other hash buckets in case this makes the
- * hash bucket empty (common). So it's much better to just mark
- * it.
- */
-static void remove_hash_entry(struct index_state *istate, struct cache_entry *ce)
-{
- ce->ce_flags |= CE_UNHASHED;
-}
-
static void replace_index_entry(struct index_state *istate, int nr, struct cache_entry *ce)
{
struct cache_entry *old = istate->cache[nr];
- if (ce != old) {
- remove_hash_entry(istate, old);
- set_index_entry(istate, nr, ce);
- }
+ remove_index_entry(old);
+ set_index_entry(istate, nr, ce);
istate->cache_changed = 1;
}
@@ -413,7 +403,7 @@ int remove_index_entry_at(struct index_state *istate, int pos)
{
struct cache_entry *ce = istate->cache[pos];
- remove_hash_entry(istate, ce);
+ remove_index_entry(ce);
istate->cache_changed = 1;
istate->cache_nr--;
if (pos >= istate->cache_nr)
@@ -1176,6 +1166,16 @@ int discard_index(struct index_state *istate)
return 0;
}
+int unmerged_index(struct index_state *istate)
+{
+ int i;
+ for (i = 0; i < istate->cache_nr; i++) {
+ if (ce_stage(istate->cache[i]))
+ return 1;
+ }
+ return 0;
+}
+
#define WRITE_BUFFER_SIZE 8192
static unsigned char write_buffer[WRITE_BUFFER_SIZE];
static unsigned long write_buffer_len;
diff --git a/receive-pack.c b/receive-pack.c
index 326749583..a971433db 100644
--- a/receive-pack.c
+++ b/receive-pack.c
@@ -132,6 +132,7 @@ static int run_hook(const char *hook_name)
break;
}
}
+ close(proc.in);
return hook_status(finish_command(&proc), hook_name);
}
@@ -414,6 +415,7 @@ static const char *unpack(void)
if (start_command(&ip))
return "index-pack fork failed";
pack_lockfile = index_pack_lockfile(ip.out);
+ close(ip.out);
status = finish_command(&ip);
if (!status) {
reprepare_packed_git();
diff --git a/refs.c b/refs.c
index 67d2a502a..c979fb1d9 100644
--- a/refs.c
+++ b/refs.c
@@ -157,6 +157,7 @@ static struct cached_refs {
struct ref_list *loose;
struct ref_list *packed;
} cached_refs;
+static struct ref_list *current_ref;
static void free_ref_list(struct ref_list *list)
{
@@ -476,6 +477,7 @@ static int do_one_ref(const char *base, each_ref_fn fn, int trim,
error("%s does not point to a valid object!", entry->name);
return 0;
}
+ current_ref = entry;
return fn(entry->name + trim, entry->sha1, entry->flag, cb_data);
}
@@ -485,6 +487,16 @@ int peel_ref(const char *ref, unsigned char *sha1)
unsigned char base[20];
struct object *o;
+ if (current_ref && (current_ref->name == ref
+ || !strcmp(current_ref->name, ref))) {
+ if (current_ref->flag & REF_KNOWS_PEELED) {
+ hashcpy(sha1, current_ref->peeled);
+ return 0;
+ }
+ hashcpy(base, current_ref->sha1);
+ goto fallback;
+ }
+
if (!resolve_ref(ref, base, 1, &flag))
return -1;
@@ -504,9 +516,9 @@ int peel_ref(const char *ref, unsigned char *sha1)
}
}
- /* fallback - callers should not call this for unpacked refs */
+fallback:
o = parse_object(base);
- if (o->type == OBJ_TAG) {
+ if (o && o->type == OBJ_TAG) {
o = deref_tag(o, ref, 0);
if (o) {
hashcpy(sha1, o->sha1);
@@ -519,7 +531,7 @@ int peel_ref(const char *ref, unsigned char *sha1)
static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
void *cb_data)
{
- int retval;
+ int retval = 0;
struct ref_list *packed = get_packed_refs();
struct ref_list *loose = get_loose_refs();
@@ -539,15 +551,18 @@ static int do_for_each_ref(const char *base, each_ref_fn fn, int trim,
}
retval = do_one_ref(base, fn, trim, cb_data, entry);
if (retval)
- return retval;
+ goto end_each;
}
for (packed = packed ? packed : loose; packed; packed = packed->next) {
retval = do_one_ref(base, fn, trim, cb_data, packed);
if (retval)
- return retval;
+ goto end_each;
}
- return 0;
+
+end_each:
+ current_ref = NULL;
+ return retval;
}
int head_ref(each_ref_fn fn, void *cb_data)
diff --git a/remote.c b/remote.c
index ae1ef5708..7e1937286 100644
--- a/remote.c
+++ b/remote.c
@@ -2,123 +2,184 @@
#include "remote.h"
#include "refs.h"
+struct counted_string {
+ size_t len;
+ const char *s;
+};
+struct rewrite {
+ const char *base;
+ size_t baselen;
+ struct counted_string *instead_of;
+ int instead_of_nr;
+ int instead_of_alloc;
+};
+
static struct remote **remotes;
-static int allocated_remotes;
+static int remotes_alloc;
+static int remotes_nr;
static struct branch **branches;
-static int allocated_branches;
+static int branches_alloc;
+static int branches_nr;
static struct branch *current_branch;
static const char *default_remote_name;
+static struct rewrite **rewrite;
+static int rewrite_alloc;
+static int rewrite_nr;
+
#define BUF_SIZE (2048)
static char buffer[BUF_SIZE];
+static const char *alias_url(const char *url)
+{
+ int i, j;
+ char *ret;
+ struct counted_string *longest;
+ int longest_i;
+
+ longest = NULL;
+ longest_i = -1;
+ for (i = 0; i < rewrite_nr; i++) {
+ if (!rewrite[i])
+ continue;
+ for (j = 0; j < rewrite[i]->instead_of_nr; j++) {
+ if (!prefixcmp(url, rewrite[i]->instead_of[j].s) &&
+ (!longest ||
+ longest->len < rewrite[i]->instead_of[j].len)) {
+ longest = &(rewrite[i]->instead_of[j]);
+ longest_i = i;
+ }
+ }
+ }
+ if (!longest)
+ return url;
+
+ ret = malloc(rewrite[longest_i]->baselen +
+ (strlen(url) - longest->len) + 1);
+ strcpy(ret, rewrite[longest_i]->base);
+ strcpy(ret + rewrite[longest_i]->baselen, url + longest->len);
+ return ret;
+}
+
static void add_push_refspec(struct remote *remote, const char *ref)
{
- int nr = remote->push_refspec_nr + 1;
- remote->push_refspec =
- xrealloc(remote->push_refspec, nr * sizeof(char *));
- remote->push_refspec[nr-1] = ref;
- remote->push_refspec_nr = nr;
+ ALLOC_GROW(remote->push_refspec,
+ remote->push_refspec_nr + 1,
+ remote->push_refspec_alloc);
+ remote->push_refspec[remote->push_refspec_nr++] = ref;
}
static void add_fetch_refspec(struct remote *remote, const char *ref)
{
- int nr = remote->fetch_refspec_nr + 1;
- remote->fetch_refspec =
- xrealloc(remote->fetch_refspec, nr * sizeof(char *));
- remote->fetch_refspec[nr-1] = ref;
- remote->fetch_refspec_nr = nr;
+ ALLOC_GROW(remote->fetch_refspec,
+ remote->fetch_refspec_nr + 1,
+ remote->fetch_refspec_alloc);
+ remote->fetch_refspec[remote->fetch_refspec_nr++] = ref;
}
static void add_url(struct remote *remote, const char *url)
{
- int nr = remote->url_nr + 1;
- remote->url =
- xrealloc(remote->url, nr * sizeof(char *));
- remote->url[nr-1] = url;
- remote->url_nr = nr;
+ ALLOC_GROW(remote->url, remote->url_nr + 1, remote->url_alloc);
+ remote->url[remote->url_nr++] = url;
+}
+
+static void add_url_alias(struct remote *remote, const char *url)
+{
+ add_url(remote, alias_url(url));
}
static struct remote *make_remote(const char *name, int len)
{
- int i, empty = -1;
+ struct remote *ret;
+ int i;
- for (i = 0; i < allocated_remotes; i++) {
- if (!remotes[i]) {
- if (empty < 0)
- empty = i;
- } else {
- if (len ? (!strncmp(name, remotes[i]->name, len) &&
- !remotes[i]->name[len]) :
- !strcmp(name, remotes[i]->name))
- return remotes[i];
- }
+ for (i = 0; i < remotes_nr; i++) {
+ if (len ? (!strncmp(name, remotes[i]->name, len) &&
+ !remotes[i]->name[len]) :
+ !strcmp(name, remotes[i]->name))
+ return remotes[i];
}
- if (empty < 0) {
- empty = allocated_remotes;
- allocated_remotes += allocated_remotes ? allocated_remotes : 1;
- remotes = xrealloc(remotes,
- sizeof(*remotes) * allocated_remotes);
- memset(remotes + empty, 0,
- (allocated_remotes - empty) * sizeof(*remotes));
- }
- remotes[empty] = xcalloc(1, sizeof(struct remote));
+ ret = xcalloc(1, sizeof(struct remote));
+ ALLOC_GROW(remotes, remotes_nr + 1, remotes_alloc);
+ remotes[remotes_nr++] = ret;
if (len)
- remotes[empty]->name = xstrndup(name, len);
+ ret->name = xstrndup(name, len);
else
- remotes[empty]->name = xstrdup(name);
- return remotes[empty];
+ ret->name = xstrdup(name);
+ return ret;
}
static void add_merge(struct branch *branch, const char *name)
{
- int nr = branch->merge_nr + 1;
- branch->merge_name =
- xrealloc(branch->merge_name, nr * sizeof(char *));
- branch->merge_name[nr-1] = name;
- branch->merge_nr = nr;
+ ALLOC_GROW(branch->merge_name, branch->merge_nr + 1,
+ branch->merge_alloc);
+ branch->merge_name[branch->merge_nr++] = name;
}
static struct branch *make_branch(const char *name, int len)
{
- int i, empty = -1;
+ struct branch *ret;
+ int i;
char *refname;
- for (i = 0; i < allocated_branches; i++) {
- if (!branches[i]) {
- if (empty < 0)
- empty = i;
- } else {
- if (len ? (!strncmp(name, branches[i]->name, len) &&
- !branches[i]->name[len]) :
- !strcmp(name, branches[i]->name))
- return branches[i];
- }
+ for (i = 0; i < branches_nr; i++) {
+ if (len ? (!strncmp(name, branches[i]->name, len) &&
+ !branches[i]->name[len]) :
+ !strcmp(name, branches[i]->name))
+ return branches[i];
}
- if (empty < 0) {
- empty = allocated_branches;
- allocated_branches += allocated_branches ? allocated_branches : 1;
- branches = xrealloc(branches,
- sizeof(*branches) * allocated_branches);
- memset(branches + empty, 0,
- (allocated_branches - empty) * sizeof(*branches));
- }
- branches[empty] = xcalloc(1, sizeof(struct branch));
+ ALLOC_GROW(branches, branches_nr + 1, branches_alloc);
+ ret = xcalloc(1, sizeof(struct branch));
+ branches[branches_nr++] = ret;
if (len)
- branches[empty]->name = xstrndup(name, len);
+ ret->name = xstrndup(name, len);
else
- branches[empty]->name = xstrdup(name);
+ ret->name = xstrdup(name);
refname = malloc(strlen(name) + strlen("refs/heads/") + 1);
strcpy(refname, "refs/heads/");
- strcpy(refname + strlen("refs/heads/"),
- branches[empty]->name);
- branches[empty]->refname = refname;
+ strcpy(refname + strlen("refs/heads/"), ret->name);
+ ret->refname = refname;
+
+ return ret;
+}
+
+static struct rewrite *make_rewrite(const char *base, int len)
+{
+ struct rewrite *ret;
+ int i;
+
+ for (i = 0; i < rewrite_nr; i++) {
+ if (len
+ ? (len == rewrite[i]->baselen &&
+ !strncmp(base, rewrite[i]->base, len))
+ : !strcmp(base, rewrite[i]->base))
+ return rewrite[i];
+ }
- return branches[empty];
+ ALLOC_GROW(rewrite, rewrite_nr + 1, rewrite_alloc);
+ ret = xcalloc(1, sizeof(struct rewrite));
+ rewrite[rewrite_nr++] = ret;
+ if (len) {
+ ret->base = xstrndup(base, len);
+ ret->baselen = len;
+ }
+ else {
+ ret->base = xstrdup(base);
+ ret->baselen = strlen(base);
+ }
+ return ret;
+}
+
+static void add_instead_of(struct rewrite *rewrite, const char *instead_of)
+{
+ ALLOC_GROW(rewrite->instead_of, rewrite->instead_of_nr + 1, rewrite->instead_of_alloc);
+ rewrite->instead_of[rewrite->instead_of_nr].s = instead_of;
+ rewrite->instead_of[rewrite->instead_of_nr].len = strlen(instead_of);
+ rewrite->instead_of_nr++;
}
static void read_remotes_file(struct remote *remote)
@@ -154,7 +215,7 @@ static void read_remotes_file(struct remote *remote)
switch (value_list) {
case 0:
- add_url(remote, xstrdup(s));
+ add_url_alias(remote, xstrdup(s));
break;
case 1:
add_push_refspec(remote, xstrdup(s));
@@ -206,7 +267,7 @@ static void read_branches_file(struct remote *remote)
} else {
branch = "refs/heads/master";
}
- add_url(remote, p);
+ add_url_alias(remote, p);
add_fetch_refspec(remote, branch);
remote->fetch_tags = 1; /* always auto-follow */
}
@@ -236,6 +297,19 @@ static int handle_config(const char *key, const char *value)
}
return 0;
}
+ if (!prefixcmp(key, "url.")) {
+ struct rewrite *rewrite;
+ name = key + 5;
+ subkey = strrchr(name, '.');
+ if (!subkey)
+ return 0;
+ rewrite = make_rewrite(name, subkey - name);
+ if (!strcmp(subkey, ".insteadof")) {
+ if (!value)
+ return config_error_nonbool(key);
+ add_instead_of(rewrite, xstrdup(value));
+ }
+ }
if (prefixcmp(key, "remote."))
return 0;
name = key + 7;
@@ -287,6 +361,18 @@ static int handle_config(const char *key, const char *value)
return 0;
}
+static void alias_all_urls(void)
+{
+ int i, j;
+ for (i = 0; i < remotes_nr; i++) {
+ if (!remotes[i])
+ continue;
+ for (j = 0; j < remotes[i]->url_nr; j++) {
+ remotes[i]->url[j] = alias_url(remotes[i]->url[j]);
+ }
+ }
+}
+
static void read_config(void)
{
unsigned char sha1[20];
@@ -303,6 +389,7 @@ static void read_config(void)
make_branch(head_ref + strlen("refs/heads/"), 0);
}
git_config(handle_config);
+ alias_all_urls();
}
struct refspec *parse_ref_spec(int nr_refspec, const char **refspec)
@@ -368,7 +455,7 @@ struct remote *remote_get(const char *name)
read_branches_file(ret);
}
if (!ret->url)
- add_url(ret, name);
+ add_url_alias(ret, name);
if (!ret->url)
return NULL;
ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec);
@@ -380,7 +467,7 @@ int for_each_remote(each_remote_fn fn, void *priv)
{
int i, result = 0;
read_config();
- for (i = 0; i < allocated_remotes && !result; i++) {
+ for (i = 0; i < remotes_nr && !result; i++) {
struct remote *r = remotes[i];
if (!r)
continue;
@@ -642,9 +729,17 @@ static int match_explicit(struct ref *src, struct ref *dst,
errs = 1;
if (!dst_value) {
+ unsigned char sha1[20];
+ int flag;
+
if (!matched_src)
return errs;
- dst_value = matched_src->name;
+ dst_value = resolve_ref(matched_src->name, sha1, 1, &flag);
+ if (!dst_value ||
+ ((flag & REF_ISSYMREF) &&
+ prefixcmp(dst_value, "refs/heads/")))
+ die("%s cannot be resolved to branch.",
+ matched_src->name);
}
switch (count_refspec_match(dst_value, dst, &matched_dst)) {
diff --git a/remote.h b/remote.h
index 86e036d61..0f6033fb2 100644
--- a/remote.h
+++ b/remote.h
@@ -6,14 +6,17 @@ struct remote {
const char **url;
int url_nr;
+ int url_alloc;
const char **push_refspec;
struct refspec *push;
int push_refspec_nr;
+ int push_refspec_alloc;
const char **fetch_refspec;
struct refspec *fetch;
int fetch_refspec_nr;
+ int fetch_refspec_alloc;
/*
* -1 to never fetch tags
@@ -100,6 +103,7 @@ struct branch {
const char **merge_name;
struct refspec **merge;
int merge_nr;
+ int merge_alloc;
};
struct branch *branch_get(const char *name);
diff --git a/revision.c b/revision.c
index d3e865810..84fbdd3af 100644
--- a/revision.c
+++ b/revision.c
@@ -738,6 +738,10 @@ void init_revisions(struct rev_info *revs, const char *prefix)
revs->commit_format = CMIT_FMT_DEFAULT;
diff_setup(&revs->diffopt);
+ if (prefix && !revs->diffopt.prefix) {
+ revs->diffopt.prefix = prefix;
+ revs->diffopt.prefix_length = strlen(prefix);
+ }
}
static void add_pending_commit_list(struct rev_info *revs,
@@ -942,6 +946,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
int left = 1;
int all_match = 0;
int regflags = 0;
+ int fixed = 0;
/* First, search for "--" */
seen_dashdash = 0;
@@ -1238,6 +1243,11 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
regflags |= REG_ICASE;
continue;
}
+ if (!strcmp(arg, "--fixed-strings") ||
+ !strcmp(arg, "-F")) {
+ fixed = 1;
+ continue;
+ }
if (!strcmp(arg, "--all-match")) {
all_match = 1;
continue;
@@ -1293,8 +1303,10 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
}
}
- if (revs->grep_filter)
+ if (revs->grep_filter) {
revs->grep_filter->regflags |= regflags;
+ revs->grep_filter->fixed = fixed;
+ }
if (show_merge)
prepare_show_merge(revs);
diff --git a/revision.h b/revision.h
index b5f01f830..c8b3b948e 100644
--- a/revision.h
+++ b/revision.h
@@ -75,7 +75,7 @@ struct rev_info {
struct log_info *loginfo;
int nr, total;
const char *mime_boundary;
- const char *message_id;
+ char *message_id;
const char *ref_message_id;
const char *add_signoff;
const char *extra_headers;
diff --git a/run-command.c b/run-command.c
index 476d00c21..743757c36 100644
--- a/run-command.c
+++ b/run-command.c
@@ -20,12 +20,19 @@ int start_command(struct child_process *cmd)
int need_in, need_out, need_err;
int fdin[2], fdout[2], fderr[2];
+ /*
+ * In case of errors we must keep the promise to close FDs
+ * that have been passed in via ->in and ->out.
+ */
+
need_in = !cmd->no_stdin && cmd->in < 0;
if (need_in) {
- if (pipe(fdin) < 0)
+ if (pipe(fdin) < 0) {
+ if (cmd->out > 0)
+ close(cmd->out);
return -ERR_RUN_COMMAND_PIPE;
+ }
cmd->in = fdin[1];
- cmd->close_in = 1;
}
need_out = !cmd->no_stdout
@@ -35,10 +42,11 @@ int start_command(struct child_process *cmd)
if (pipe(fdout) < 0) {
if (need_in)
close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
return -ERR_RUN_COMMAND_PIPE;
}
cmd->out = fdout[0];
- cmd->close_out = 1;
}
need_err = !cmd->no_stderr && cmd->err < 0;
@@ -46,8 +54,12 @@ int start_command(struct child_process *cmd)
if (pipe(fderr) < 0) {
if (need_in)
close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
if (need_out)
close_pair(fdout);
+ else if (cmd->out)
+ close(cmd->out);
return -ERR_RUN_COMMAND_PIPE;
}
cmd->err = fderr[0];
@@ -57,8 +69,12 @@ int start_command(struct child_process *cmd)
if (cmd->pid < 0) {
if (need_in)
close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
if (need_out)
close_pair(fdout);
+ else if (cmd->out)
+ close(cmd->out);
if (need_err)
close_pair(fderr);
return -ERR_RUN_COMMAND_FORK;
@@ -120,7 +136,7 @@ int start_command(struct child_process *cmd)
if (need_out)
close(fdout[1]);
- else if (cmd->out > 1)
+ else if (cmd->out)
close(cmd->out);
if (need_err)
@@ -157,10 +173,6 @@ static int wait_or_whine(pid_t pid)
int finish_command(struct child_process *cmd)
{
- if (cmd->close_in)
- close(cmd->in);
- if (cmd->close_out)
- close(cmd->out);
return wait_or_whine(cmd->pid);
}
diff --git a/run-command.h b/run-command.h
index 1fc781d76..debe3074b 100644
--- a/run-command.h
+++ b/run-command.h
@@ -14,13 +14,29 @@ enum {
struct child_process {
const char **argv;
pid_t pid;
+ /*
+ * Using .in, .out, .err:
+ * - Specify 0 for no redirections (child inherits stdin, stdout,
+ * stderr from parent).
+ * - Specify -1 to have a pipe allocated as follows:
+ * .in: returns the writable pipe end; parent writes to it,
+ * the readable pipe end becomes child's stdin
+ * .out, .err: returns the readable pipe end; parent reads from
+ * it, the writable pipe end becomes child's stdout/stderr
+ * The caller of start_command() must close the returned FDs
+ * after it has completed reading from/writing to it!
+ * - Specify > 0 to set a channel to a particular FD as follows:
+ * .in: a readable FD, becomes child's stdin
+ * .out: a writable FD, becomes child's stdout/stderr
+ * .err > 0 not supported
+ * The specified FD is closed by start_command(), even in case
+ * of errors!
+ */
int in;
int out;
int err;
const char *dir;
const char *const *env;
- unsigned close_in:1;
- unsigned close_out:1;
unsigned no_stdin:1;
unsigned no_stdout:1;
unsigned no_stderr:1;
diff --git a/shortlog.h b/shortlog.h
new file mode 100644
index 000000000..31ff491b7
--- /dev/null
+++ b/shortlog.h
@@ -0,0 +1,26 @@
+#ifndef SHORTLOG_H
+#define SHORTLOG_H
+
+#include "path-list.h"
+
+struct shortlog {
+ struct path_list list;
+ int summary;
+ int wrap_lines;
+ int sort_by_number;
+ int wrap;
+ int in1;
+ int in2;
+
+ char *common_repo_prefix;
+ int email;
+ struct path_list mailmap;
+};
+
+void shortlog_init(struct shortlog *log);
+
+void shortlog_add_commit(struct shortlog *log, struct commit *commit);
+
+void shortlog_output(struct shortlog *log);
+
+#endif
diff --git a/t/t0050-filesystem.sh b/t/t0050-filesystem.sh
new file mode 100755
index 000000000..cd088b37f
--- /dev/null
+++ b/t/t0050-filesystem.sh
@@ -0,0 +1,93 @@
+#!/bin/sh
+
+test_description='Various filesystem issues'
+
+. ./test-lib.sh
+
+auml=`perl -CO -e 'print pack("U",0x00E4)'`
+aumlcdiar=`perl -CO -e 'print pack("U",0x0061).pack("U",0x0308)'`
+
+test_expect_success 'see if we expect ' '
+
+ test_case=test_expect_success
+ test_unicode=test_expect_success
+ mkdir junk &&
+ echo good >junk/CamelCase &&
+ echo bad >junk/camelcase &&
+ if test "$(cat junk/CamelCase)" != good
+ then
+ test_case=test_expect_failure
+ say "will test on a case insensitive filesystem"
+ fi &&
+ rm -fr junk &&
+ mkdir junk &&
+ >junk/"$auml" &&
+ case "$(cd junk && echo *)" in
+ "$aumlcdiar")
+ test_unicode=test_expect_failure
+ say "will test on a unicode corrupting filesystem"
+ ;;
+ *) ;;
+ esac &&
+ rm -fr junk
+'
+
+test_expect_success "setup case tests" '
+
+ touch camelcase &&
+ git add camelcase &&
+ git commit -m "initial" &&
+ git tag initial &&
+ git checkout -b topic &&
+ git mv camelcase tmp &&
+ git mv tmp CamelCase &&
+ git commit -m "rename" &&
+ git checkout -f master
+
+'
+
+$test_case 'rename (case change)' '
+
+ git mv camelcase CamelCase &&
+ git commit -m "rename"
+
+'
+
+$test_case 'merge (case change)' '
+
+ git reset --hard initial &&
+ git merge topic
+
+'
+
+test_expect_success "setup unicode normalization tests" '
+
+ test_create_repo unicode &&
+ cd unicode &&
+ touch "$aumlcdiar" &&
+ git add "$aumlcdiar" &&
+ git commit -m initial
+ git tag initial &&
+ git checkout -b topic &&
+ git mv $aumlcdiar tmp &&
+ git mv tmp "$auml" &&
+ git commit -m rename &&
+ git checkout -f master
+
+'
+
+$test_unicode 'rename (silent unicode normalization)' '
+
+ git mv "$aumlcdiar" "$auml" &&
+ git commit -m rename
+
+'
+
+$test_unicode 'merge (silent unicode normalization)' '
+
+ git reset --hard initial &&
+ git merge topic
+
+'
+
+test_done
diff --git a/t/t1502-rev-parse-parseopt.sh b/t/t1502-rev-parse-parseopt.sh
new file mode 100755
index 000000000..762af5faf
--- /dev/null
+++ b/t/t1502-rev-parse-parseopt.sh
@@ -0,0 +1,43 @@
+#!/bin/sh
+
+test_description='test git rev-parse --parseopt'
+. ./test-lib.sh
+
+cat > expect.err <<EOF
+usage: some-command [options] <args>...
+
+ some-command does foo and bar!
+
+ -h, --help show the help
+ --foo some nifty option --foo
+ --bar ... some cool option --bar with an argument
+
+An option group Header
+ -C [...] option C with an optional argument
+
+Extras
+ --extra1 line above used to cause a segfault but no longer does
+
+EOF
+
+test_expect_success 'test --parseopt help output' '
+ git rev-parse --parseopt -- -h 2> output.err <<EOF
+some-command [options] <args>...
+
+some-command does foo and bar!
+--
+h,help show the help
+
+foo some nifty option --foo
+bar= some cool option --bar with an argument
+
+ An option group Header
+C? option C with an optional argument
+
+Extras
+extra1 line above used to cause a segfault but no longer does
+EOF
+ git diff expect.err output.err
+'
+
+test_done
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index d21081d0f..38a90adad 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -15,6 +15,9 @@ test_expect_success \
'echo Hello > A &&
git update-index --add A &&
git-commit -m "Initial commit." &&
+ echo World >> A &&
+ git update-index --add A &&
+ git-commit -m "Second commit." &&
HEAD=$(git rev-parse --verify HEAD)'
test_expect_success \
@@ -171,7 +174,9 @@ test_expect_success 'test overriding tracking setup via --no-track' \
! test "$(git config branch.my2.merge)" = refs/heads/master'
test_expect_success 'no tracking without .fetch entries' \
- 'git branch --track my6 s &&
+ 'git config branch.autosetupmerge true &&
+ git branch my6 s &&
+ git config branch.automsetupmerge false &&
test -z "$(git config branch.my6.remote)" &&
test -z "$(git config branch.my6.merge)"'
@@ -192,6 +197,21 @@ test_expect_success 'test deleting branch without config' \
'git branch my7 s &&
test "$(git branch -d my7 2>&1)" = "Deleted branch my7."'
+test_expect_success 'test --track without .fetch entries' \
+ 'git branch --track my8 &&
+ test "$(git config branch.my8.remote)" &&
+ test "$(git config branch.my8.merge)"'
+
+test_expect_success \
+ 'branch from non-branch HEAD w/autosetupmerge=always' \
+ 'git config branch.autosetupmerge always &&
+ git branch my9 HEAD^ &&
+ git config branch.autosetupmerge false'
+
+test_expect_success \
+ 'branch from non-branch HEAD w/--track causes failure' \
+ '!(git branch --track my10 HEAD^)'
+
# Keep this test last, as it changes the current branch
cat >expect <<EOF
0000000000000000000000000000000000000000 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
diff --git a/t/t4013-diff-various.sh b/t/t4013-diff-various.sh
index 9eec75422..6b4d1c52b 100755
--- a/t/t4013-diff-various.sh
+++ b/t/t4013-diff-various.sh
@@ -245,6 +245,7 @@ format-patch --inline --stdout initial..master
format-patch --inline --stdout --subject-prefix=TESTCASE initial..master
config format.subjectprefix DIFFERENT_PREFIX
format-patch --inline --stdout initial..master^^
+format-patch --stdout --cover-letter -n initial..master^
diff --abbrev initial..side
diff -r initial..side
diff --git a/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
new file mode 100644
index 000000000..0151453b7
--- /dev/null
+++ b/t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^
@@ -0,0 +1,100 @@
+$ git format-patch --stdout --cover-letter -n initial..master^
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: C O Mitter <committer@example.com>
+Date: Mon, 26 Jun 2006 00:05:00 +0000
+Subject: [DIFFERENT_PREFIX 0/2] *** SUBJECT HERE ***
+
+*** BLURB HERE ***
+
+A U Thor (2):
+ Second
+ Third
+
+ dir/sub | 4 ++++
+ file0 | 3 +++
+ file1 | 3 +++
+ file2 | 3 ---
+ 4 files changed, 10 insertions(+), 3 deletions(-)
+ create mode 100644 file1
+ delete mode 100644 file2
+
+From 1bde4ae5f36c8d9abe3a0fce0c6aab3c4a12fe44 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:01:00 +0000
+Subject: [DIFFERENT_PREFIX 1/2] Second
+
+This is the second commit.
+---
+ dir/sub | 2 ++
+ file0 | 3 +++
+ file2 | 3 ---
+ 3 files changed, 5 insertions(+), 3 deletions(-)
+ delete mode 100644 file2
+
+diff --git a/dir/sub b/dir/sub
+index 35d242b..8422d40 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -1,2 +1,4 @@
+ A
+ B
++C
++D
+diff --git a/file0 b/file0
+index 01e79c3..b414108 100644
+--- a/file0
++++ b/file0
+@@ -1,3 +1,6 @@
+ 1
+ 2
+ 3
++4
++5
++6
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 01e79c3..0000000
+--- a/file2
++++ /dev/null
+@@ -1,3 +0,0 @@
+-1
+-2
+-3
+--
+g-i-t--v-e-r-s-i-o-n
+
+
+From 9a6d4949b6b76956d9d5e26f2791ec2ceff5fdc0 Mon Sep 17 00:00:00 2001
+From: A U Thor <author@example.com>
+Date: Mon, 26 Jun 2006 00:02:00 +0000
+Subject: [DIFFERENT_PREFIX 2/2] Third
+
+---
+ dir/sub | 2 ++
+ file1 | 3 +++
+ 2 files changed, 5 insertions(+), 0 deletions(-)
+ create mode 100644 file1
+
+diff --git a/dir/sub b/dir/sub
+index 8422d40..cead32e 100644
+--- a/dir/sub
++++ b/dir/sub
+@@ -2,3 +2,5 @@ A
+ B
+ C
+ D
++E
++F
+diff --git a/file1 b/file1
+new file mode 100644
+index 0000000..b1e6722
+--- /dev/null
++++ b/file1
+@@ -0,0 +1,3 @@
++A
++B
++C
+--
+g-i-t--v-e-r-s-i-o-n
+
+$
diff --git a/t/t4014-format-patch.sh b/t/t4014-format-patch.sh
index 0a6fe5337..16aa99dc0 100755
--- a/t/t4014-format-patch.sh
+++ b/t/t4014-format-patch.sh
@@ -88,4 +88,117 @@ test_expect_success 'replay did not screw up the log message' '
'
+test_expect_success 'extra headers' '
+
+ git config format.headers "To: R. E. Cipient <rcipient@example.com>
+" &&
+ git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
+" &&
+ git format-patch --stdout master..side > patch2 &&
+ sed -e "/^$/q" patch2 > hdrs2 &&
+ grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
+ grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
+
+'
+
+test_expect_success 'extra headers without newlines' '
+
+ git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
+ git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
+ git format-patch --stdout master..side >patch3 &&
+ sed -e "/^$/q" patch3 > hdrs3 &&
+ grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
+ grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
+
+'
+
+test_expect_success 'extra headers with multiple To:s' '
+
+ git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
+ git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
+ git format-patch --stdout master..side > patch4 &&
+ sed -e "/^$/q" patch4 > hdrs4 &&
+ grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
+ grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
+'
+
+test_expect_success 'additional command line cc' '
+
+ git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
+ git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
+ grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
+ grep "^ *S. E. Cipient <scipient@example.com>$" patch5
+'
+
+test_expect_success 'multiple files' '
+
+ rm -rf patches/ &&
+ git checkout side &&
+ git format-patch -o patches/ master &&
+ ls patches/0001-Side-changes-1.patch patches/0002-Side-changes-2.patch patches/0003-Side-changes-3-with-n-backslash-n-in-it.patch
+'
+
+test_expect_success 'thread' '
+
+ rm -rf patches/ &&
+ git checkout side &&
+ git format-patch --thread -o patches/ master &&
+ FIRST_MID=$(grep "Message-Id:" patches/0001-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
+ for i in patches/0002-* patches/0003-*
+ do
+ grep "References: $FIRST_MID" $i &&
+ grep "In-Reply-To: $FIRST_MID" $i
+ done
+'
+
+test_expect_success 'thread in-reply-to' '
+
+ rm -rf patches/ &&
+ git checkout side &&
+ git format-patch --in-reply-to="<test.message>" --thread -o patches/ master &&
+ FIRST_MID="<test.message>" &&
+ for i in patches/*
+ do
+ grep "References: $FIRST_MID" $i &&
+ grep "In-Reply-To: $FIRST_MID" $i
+ done
+'
+
+test_expect_success 'thread cover-letter' '
+
+ rm -rf patches/ &&
+ git checkout side &&
+ git format-patch --cover-letter --thread -o patches/ master &&
+ FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
+ for i in patches/0001-* patches/0002-* patches/0003-*
+ do
+ grep "References: $FIRST_MID" $i &&
+ grep "In-Reply-To: $FIRST_MID" $i
+ done
+'
+
+test_expect_success 'thread cover-letter in-reply-to' '
+
+ rm -rf patches/ &&
+ git checkout side &&
+ git format-patch --cover-letter --in-reply-to="<test.message>" --thread -o patches/ master &&
+ FIRST_MID="<test.message>" &&
+ for i in patches/*
+ do
+ grep "References: $FIRST_MID" $i &&
+ grep "In-Reply-To: $FIRST_MID" $i
+ done
+'
+
+test_expect_success 'excessive subject' '
+
+ rm -rf patches/ &&
+ git checkout side &&
+ for i in 5 6 1 2 3 A 4 B C 7 8 9 10 D E F; do echo "$i"; done >>file &&
+ git update-index file &&
+ git commit -m "This is an excessively long subject line for a message due to the habit some projects have of not having a short, one-line subject at the start of the commit message, but rather sticking a whole paragraph right at the start as the only thing in the commit message. It had better not become the filename for the patch." &&
+ git format-patch -o patches/ master..side &&
+ ls patches/0004-This-is-an-excessively-long-subject-line-for-a-messa.patch
+'
+
test_done
diff --git a/t/t4019-diff-wserror.sh b/t/t4019-diff-wserror.sh
index 67e080bdb..0d9cbb626 100755
--- a/t/t4019-diff-wserror.sh
+++ b/t/t4019-diff-wserror.sh
@@ -12,6 +12,7 @@ test_expect_success setup '
echo " Eight SP indent" >>F &&
echo " HT and SP indent" >>F &&
echo "With trailing SP " >>F &&
+ echo "Carriage ReturnQ" | tr Q "\015" >>F &&
echo "No problem" >>F
'
@@ -27,6 +28,7 @@ test_expect_success default '
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
grep With error >/dev/null &&
+ grep Return error >/dev/null &&
grep No normal >/dev/null
'
@@ -41,6 +43,7 @@ test_expect_success 'without -trail' '
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
grep No normal >/dev/null
'
@@ -56,6 +59,7 @@ test_expect_success 'without -trail (attribute)' '
grep Eight normal >/dev/null &&
grep HT error >/dev/null &&
grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
grep No normal >/dev/null
'
@@ -71,6 +75,7 @@ test_expect_success 'without -space' '
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
grep With error >/dev/null &&
+ grep Return error >/dev/null &&
grep No normal >/dev/null
'
@@ -86,6 +91,7 @@ test_expect_success 'without -space (attribute)' '
grep Eight normal >/dev/null &&
grep HT normal >/dev/null &&
grep With error >/dev/null &&
+ grep Return error >/dev/null &&
grep No normal >/dev/null
'
@@ -101,6 +107,7 @@ test_expect_success 'with indent-non-tab only' '
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
grep No normal >/dev/null
'
@@ -116,6 +123,39 @@ test_expect_success 'with indent-non-tab only (attribute)' '
grep Eight error >/dev/null &&
grep HT normal >/dev/null &&
grep With normal >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'with cr-at-eol' '
+
+ rm -f .gitattributes
+ git config core.whitespace cr-at-eol
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep Return normal >/dev/null &&
+ grep No normal >/dev/null
+
+'
+
+test_expect_success 'with cr-at-eol (attribute)' '
+
+ git config --unset core.whitespace
+ echo "F whitespace=trailing,cr-at-eol" >.gitattributes
+ git diff --color >output
+ grep "$blue_grep" output >error
+ grep -v "$blue_grep" output >normal
+
+ grep Eight normal >/dev/null &&
+ grep HT error >/dev/null &&
+ grep With error >/dev/null &&
+ grep Return normal >/dev/null &&
grep No normal >/dev/null
'
diff --git a/t/t4105-apply-fuzz.sh b/t/t4105-apply-fuzz.sh
new file mode 100755
index 000000000..0e8d25f18
--- /dev/null
+++ b/t/t4105-apply-fuzz.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+test_description='apply with fuzz and offset'
+
+. ./test-lib.sh
+
+dotest () {
+ name="$1" && shift &&
+ test_expect_success "$name" "
+ git checkout-index -f -q -u file &&
+ git apply $* &&
+ diff -u expect file
+ "
+}
+
+test_expect_success setup '
+
+ for i in 1 2 3 4 5 6 7 8 9 10 11 12
+ do
+ echo $i
+ done >file &&
+ git update-index --add file &&
+ for i in 1 2 3 4 5 6 7 a b c d e 8 9 10 11 12
+ do
+ echo $i
+ done >file &&
+ cat file >expect &&
+ git diff >O0.diff &&
+
+ sed -e "s/@@ -5,6 +5,11 @@/@@ -2,6 +2,11 @@/" >O1.diff O0.diff &&
+ sed -e "s/@@ -5,6 +5,11 @@/@@ -7,6 +7,11 @@/" >O2.diff O0.diff &&
+ sed -e "s/@@ -5,6 +5,11 @@/@@ -19,6 +19,11 @@/" >O3.diff O0.diff &&
+
+ sed -e "s/^ 5/ S/" >F0.diff O0.diff &&
+ sed -e "s/^ 5/ S/" >F1.diff O1.diff &&
+ sed -e "s/^ 5/ S/" >F2.diff O2.diff &&
+ sed -e "s/^ 5/ S/" >F3.diff O3.diff
+
+'
+
+dotest 'unmodified patch' O0.diff
+
+dotest 'minus offset' O1.diff
+
+dotest 'plus offset' O2.diff
+
+dotest 'big offset' O3.diff
+
+dotest 'fuzz with no offset' -C2 F0.diff
+
+dotest 'fuzz with minus offset' -C2 F1.diff
+
+dotest 'fuzz with plus offset' -C2 F2.diff
+
+dotest 'fuzz with big offset' -C2 F3.diff
+
+test_done
diff --git a/t/t4125-apply-ws-fuzz.sh b/t/t4125-apply-ws-fuzz.sh
new file mode 100755
index 000000000..d6f15be67
--- /dev/null
+++ b/t/t4125-apply-ws-fuzz.sh
@@ -0,0 +1,103 @@
+#!/bin/sh
+
+test_description='applying patch that has broken whitespaces in context'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ >file &&
+ git add file &&
+
+ # file-0 is full of whitespace breakages
+ for l in a bb c d eeee f ggg h
+ do
+ echo "$l "
+ done >file-0 &&
+
+ # patch-0 creates a whitespace broken file
+ cat file-0 >file &&
+ git diff >patch-0 &&
+ git add file &&
+
+ # file-1 is still full of whitespace breakages,
+ # but has one line updated, without fixing any
+ # whitespaces.
+ # patch-1 records that change.
+ sed -e "s/d/D/" file-0 >file-1 &&
+ cat file-1 >file &&
+ git diff >patch-1 &&
+
+ # patch-all is the effect of both patch-0 and patch-1
+ >file &&
+ git add file &&
+ cat file-1 >file &&
+ git diff >patch-all &&
+
+ # patch-2 is the same as patch-1 but is based
+ # on a version that already has whitespace fixed,
+ # and does not introduce whitespace breakages.
+ sed -e "s/ $//" patch-1 >patch-2 &&
+
+ # If all whitespace breakages are fixed the contents
+ # should look like file-fixed
+ sed -e "s/ $//" file-1 >file-fixed
+
+'
+
+test_expect_success nofix '
+
+ >file &&
+ git add file &&
+
+ # Baseline. Applying without fixing any whitespace
+ # breakages.
+ git apply --whitespace=nowarn patch-0 &&
+ git apply --whitespace=nowarn patch-1 &&
+
+ # The result should obviously match.
+ diff -u file-1 file
+'
+
+test_expect_success 'withfix (forward)' '
+
+ >file &&
+ git add file &&
+
+ # The first application will munge the context lines
+ # the second patch depends on. We should be able to
+ # adjust and still apply.
+ git apply --whitespace=fix patch-0 &&
+ git apply --whitespace=fix patch-1 &&
+
+ diff -u file-fixed file
+'
+
+test_expect_success 'withfix (backward)' '
+
+ >file &&
+ git add file &&
+
+ # Now we have a whitespace breakages on our side.
+ git apply --whitespace=nowarn patch-0 &&
+
+ # And somebody sends in a patch based on image
+ # with whitespace already fixed.
+ git apply --whitespace=fix patch-2 &&
+
+ # The result should accept the whitespace fixed
+ # postimage. But the line with "h" is beyond context
+ # horizon and left unfixed.
+
+ sed -e /h/d file-fixed >fixed-head &&
+ sed -e /h/d file >file-head &&
+ diff -u fixed-head file-head &&
+
+ sed -n -e /h/p file-fixed >fixed-tail &&
+ sed -n -e /h/p file >file-tail &&
+
+ ! diff -u fixed-tail file-tail
+
+'
+
+test_done
diff --git a/t/t5303-hash-object.sh b/t/t5303-hash-object.sh
new file mode 100755
index 000000000..543c0784b
--- /dev/null
+++ b/t/t5303-hash-object.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+test_description=git-hash-object
+
+. ./test-lib.sh
+
+test_expect_success \
+ 'git hash-object -w --stdin saves the object' \
+ 'obname=$(echo foo | git hash-object -w --stdin) &&
+ obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
+ test -r .git/objects/"$obpath" &&
+ rm -f .git/objects/"$obpath"'
+
+test_expect_success \
+ 'git hash-object --stdin -w saves the object' \
+ 'obname=$(echo foo | git hash-object --stdin -w) &&
+ obpath=$(echo $obname | sed -e "s/\(..\)/\1\//") &&
+ test -r .git/objects/"$obpath" &&
+ rm -f .git/objects/"$obpath"'
+
+test_expect_success \
+ 'git hash-object --stdin file1 <file0 first operates on file0, then file1' \
+ 'echo foo > file1 &&
+ obname0=$(echo bar | git hash-object --stdin) &&
+ obname1=$(git hash-object file1) &&
+ obname0new=$(echo bar | git hash-object --stdin file1 | sed -n -e 1p) &&
+ obname1new=$(echo bar | git hash-object --stdin file1 | sed -n -e 2p) &&
+ test "$obname0" = "$obname0new" &&
+ test "$obname1" = "$obname1new"'
+
+test_expect_success \
+ 'git hash-object refuses multiple --stdin arguments' \
+ '! git hash-object --stdin --stdin < file1'
+
+test_done
diff --git a/t/t5516-fetch-push.sh b/t/t5516-fetch-push.sh
index 9d2dc33cb..793ffc660 100755
--- a/t/t5516-fetch-push.sh
+++ b/t/t5516-fetch-push.sh
@@ -100,6 +100,23 @@ test_expect_success 'fetch with wildcard' '
)
'
+test_expect_success 'fetch with insteadOf' '
+ mk_empty &&
+ (
+ TRASH=$(pwd) &&
+ cd testrepo &&
+ git config url./$TRASH/.insteadOf trash/
+ git config remote.up.url trash/. &&
+ git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
+ git fetch up &&
+
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+ test "z$r" = "z$the_commit" &&
+
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ )
+'
+
test_expect_success 'push without wildcard' '
mk_empty &&
@@ -126,6 +143,20 @@ test_expect_success 'push with wildcard' '
)
'
+test_expect_success 'push with insteadOf' '
+ mk_empty &&
+ TRASH=$(pwd) &&
+ git config url./$TRASH/.insteadOf trash/ &&
+ git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
+ (
+ cd testrepo &&
+ r=$(git show-ref -s --verify refs/remotes/origin/master) &&
+ test "z$r" = "z$the_commit" &&
+
+ test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ )
+'
+
test_expect_success 'push with matching heads' '
mk_test heads/master &&
@@ -271,6 +302,49 @@ test_expect_success 'push with HEAD nonexisting at remote' '
check_push_result $the_commit heads/local
'
+test_expect_success 'push with +HEAD' '
+
+ mk_test heads/master &&
+ git checkout master &&
+ git branch -D local &&
+ git checkout -b local &&
+ git push testrepo master local &&
+ check_push_result $the_commit heads/master &&
+ check_push_result $the_commit heads/local &&
+
+ # Without force rewinding should fail
+ git reset --hard HEAD^ &&
+ ! git push testrepo HEAD &&
+ check_push_result $the_commit heads/local &&
+
+ # With force rewinding should succeed
+ git push testrepo +HEAD &&
+ check_push_result $the_first_commit heads/local
+
+'
+
+test_expect_success 'push with config remote.*.push = HEAD' '
+
+ mk_test heads/local &&
+ git checkout master &&
+ git branch -f local $the_commit &&
+ (
+ cd testrepo &&
+ git checkout local &&
+ git reset --hard $the_first_commit
+ ) &&
+ git config remote.there.url testrepo &&
+ git config remote.there.push HEAD &&
+ git config branch.master.remote there &&
+ git push &&
+ check_push_result $the_commit heads/master &&
+ check_push_result $the_first_commit heads/local
+'
+
+# clean up the cruft left with the previous one
+git config --remove-section remote.there
+git config --remove-section branch.master
+
test_expect_success 'push with dry-run' '
mk_test heads/master &&
diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh
index 86419964b..79dc58b2c 100755
--- a/t/t6023-merge-file.sh
+++ b/t/t6023-merge-file.sh
@@ -139,4 +139,24 @@ test_expect_success 'binary files cannot be merged' '
grep "Cannot merge binary files" merge.err
'
+sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt
+sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt
+
+test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
+
+ ! git merge-file -p new6.txt new5.txt new7.txt > output &&
+ test 1 = $(grep ======= < output | wc -l)
+
+'
+
+sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit;/" < new6.txt > new8.txt
+sed -e 's/deerit./&\n\n\n\n/' -e "s/locavit,/locavit --/" < new7.txt > new9.txt
+
+test_expect_success 'ZEALOUS_ALNUM' '
+
+ ! git merge-file -p new8.txt new5.txt new9.txt > merge.out &&
+ test 1 = $(grep ======= < merge.out | wc -l)
+
+'
+
test_done
diff --git a/t/t6029-merge-subtree.sh b/t/t6029-merge-subtree.sh
new file mode 100755
index 000000000..3900a0508
--- /dev/null
+++ b/t/t6029-merge-subtree.sh
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+test_description='subtree merge strategy'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+
+ s="1 2 3 4 5 6 7 8"
+ for i in $s; do echo $i; done >hello &&
+ git add hello &&
+ git commit -m initial &&
+ git checkout -b side &&
+ echo >>hello world &&
+ git add hello &&
+ git commit -m second &&
+ git checkout master &&
+ for i in mundo $s; do echo $i; done >hello &&
+ git add hello &&
+ git commit -m master
+
+'
+
+test_expect_success 'subtree available and works like recursive' '
+
+ git merge -s subtree side &&
+ for i in mundo $s world; do echo $i; done >expect &&
+ diff -u expect hello
+
+'
+
+test_done
diff --git a/t/t6030-bisect-porcelain.sh b/t/t6030-bisect-porcelain.sh
index ec71123f4..4908e878f 100755
--- a/t/t6030-bisect-porcelain.sh
+++ b/t/t6030-bisect-porcelain.sh
@@ -260,7 +260,7 @@ test_expect_success 'bisect starting with a detached HEAD' '
git checkout master^ &&
HEAD=$(git rev-parse --verify HEAD) &&
git bisect start &&
- test $HEAD = $(cat .git/head-name) &&
+ test $HEAD = $(cat .git/BISECT_START) &&
git bisect reset &&
test $HEAD = $(git rev-parse --verify HEAD)
diff --git a/t/t7201-co.sh b/t/t7201-co.sh
index dbf1ace29..63915cd87 100755
--- a/t/t7201-co.sh
+++ b/t/t7201-co.sh
@@ -83,13 +83,13 @@ test_expect_success "checkout with unrelated dirty tree without -m" '
fill 0 1 2 3 4 5 6 7 8 >same &&
cp same kept
git checkout side >messages &&
- git diff same kept
+ diff -u same kept
(cat > messages.expect <<EOF
M same
EOF
) &&
touch messages.expect &&
- git diff messages.expect messages
+ diff -u messages.expect messages
'
test_expect_success "checkout -m with dirty tree" '
@@ -103,29 +103,22 @@ test_expect_success "checkout -m with dirty tree" '
test "$(git symbolic-ref HEAD)" = "refs/heads/side" &&
(cat >expect.messages <<EOF
-Merging side with local
-Merging:
-ab76817 Side M one, D two, A three
-virtual local
-found 1 common ancestor(s):
-7329388 Initial A one, A two
-Auto-merged one
M one
EOF
) &&
- git diff expect.messages messages &&
+ diff -u expect.messages messages &&
fill "M one" "A three" "D two" >expect.master &&
git diff --name-status master >current.master &&
- diff expect.master current.master &&
+ diff -u expect.master current.master &&
fill "M one" >expect.side &&
git diff --name-status side >current.side &&
- diff expect.side current.side &&
+ diff -u expect.side current.side &&
: >expect.index &&
git diff --cached >current.index &&
- diff expect.index current.index
+ diff -u expect.index current.index
'
test_expect_success "checkout -m with dirty tree, renamed" '
@@ -143,7 +136,7 @@ test_expect_success "checkout -m with dirty tree, renamed" '
git checkout -m renamer &&
fill 1 3 4 5 7 8 >expect &&
- diff expect uno &&
+ diff -u expect uno &&
! test -f one &&
git diff --cached >current &&
! test -s current
@@ -168,7 +161,7 @@ test_expect_success 'checkout -m with merge conflict' '
git diff master:one :3:uno |
sed -e "1,/^@@/d" -e "/^ /d" -e "s/^-/d/" -e "s/^+/a/" >current &&
fill d2 aT d7 aS >expect &&
- diff current expect &&
+ diff -u current expect &&
git diff --cached two >current &&
! test -s current
'
@@ -185,7 +178,7 @@ If you want to create a new branch from this checkout, you may do so
HEAD is now at 7329388... Initial A one, A two
EOF
) &&
- git diff messages.expect messages &&
+ diff -u messages.expect messages &&
H=$(git rev-parse --verify HEAD) &&
M=$(git show-ref -s --verify refs/heads/master) &&
test "z$H" = "z$M" &&
@@ -286,4 +279,62 @@ test_expect_success 'checkout with ambiguous tag/branch names' '
'
+test_expect_success 'switch branches while in subdirectory' '
+
+ git reset --hard &&
+ git checkout master &&
+
+ mkdir subs &&
+ (
+ cd subs &&
+ git checkout side
+ ) &&
+ ! test -f subs/one &&
+ rm -fr subs
+
+'
+
+test_expect_success 'checkout specific path while in subdirectory' '
+
+ git reset --hard &&
+ git checkout side &&
+ mkdir subs &&
+ >subs/bero &&
+ git add subs/bero &&
+ git commit -m "add subs/bero" &&
+
+ git checkout master &&
+ mkdir -p subs &&
+ (
+ cd subs &&
+ git checkout side -- bero
+ ) &&
+ test -f subs/bero
+
+'
+
+test_expect_success \
+ 'checkout w/--track sets up tracking' '
+ git config branch.autosetupmerge false &&
+ git checkout master &&
+ git checkout --track -b track1 &&
+ test "$(git config branch.track1.remote)" &&
+ test "$(git config branch.track1.merge)"'
+
+test_expect_success \
+ 'checkout w/autosetupmerge=always sets up tracking' '
+ git config branch.autosetupmerge always &&
+ git checkout master &&
+ git checkout -b track2 &&
+ test "$(git config branch.track2.remote)" &&
+ test "$(git config branch.track2.merge)"
+ git config branch.autosetupmerge false'
+
+test_expect_success \
+ 'checkout w/--track from non-branch HEAD fails' '
+ git checkout -b delete-me master &&
+ rm .git/refs/heads/delete-me &&
+ test refs/heads/delete-me = "$(git symbolic-ref HEAD)" &&
+ !(git checkout --track -b track)'
+
test_done
diff --git a/t/t9001-send-email.sh b/t/t9001-send-email.sh
index 2efaed441..cbbfa9cb4 100755
--- a/t/t9001-send-email.sh
+++ b/t/t9001-send-email.sh
@@ -15,16 +15,22 @@ test_expect_success \
'Setup helper tool' \
'(echo "#!/bin/sh"
echo shift
+ echo output=1
+ echo "while test -f commandline\$output; do output=\$((\$output+1)); done"
echo for a
echo do
echo " echo \"!\$a!\""
- echo "done >commandline"
- echo "cat > msgtxt"
+ echo "done >commandline\$output"
+ echo "cat > msgtxt\$output"
) >fake.sendmail &&
chmod +x ./fake.sendmail &&
git add fake.sendmail &&
GIT_AUTHOR_NAME="A" git commit -a -m "Second."'
+clean_fake_sendmail() {
+ rm -f commandline* msgtxt*
+}
+
test_expect_success 'Extract patches' '
patches=`git format-patch -n HEAD^1`
'
@@ -39,7 +45,7 @@ cat >expected <<\EOF
EOF
test_expect_success \
'Verify commandline' \
- 'diff commandline expected'
+ 'diff commandline1 expected'
cat >expected-show-all-headers <<\EOF
0001-Second.patch
@@ -82,7 +88,7 @@ z8=zzzzzzzz
z64=$z8$z8$z8$z8$z8$z8$z8$z8
z512=$z64$z64$z64$z64$z64$z64$z64$z64
test_expect_success 'reject long lines' '
- rm -f commandline &&
+ clean_fake_sendmail &&
cp $patches longline.patch &&
echo $z512$z512 >>longline.patch &&
! git send-email \
@@ -95,7 +101,7 @@ test_expect_success 'reject long lines' '
'
test_expect_success 'no patch was sent' '
- ! test -e commandline
+ ! test -e commandline1
'
test_expect_success 'allow long lines with --no-validate' '
@@ -109,6 +115,7 @@ test_expect_success 'allow long lines with --no-validate' '
'
test_expect_success 'Invalid In-Reply-To' '
+ clean_fake_sendmail &&
git send-email \
--from="Example <nobody@example.com>" \
--to=nobody@example.com \
@@ -116,17 +123,47 @@ test_expect_success 'Invalid In-Reply-To' '
--smtp-server="$(pwd)/fake.sendmail" \
$patches
2>errors
- ! grep "^In-Reply-To: < *>" msgtxt
+ ! grep "^In-Reply-To: < *>" msgtxt1
'
test_expect_success 'Valid In-Reply-To when prompting' '
+ clean_fake_sendmail &&
(echo "From Example <from@example.com>"
echo "To Example <to@example.com>"
echo ""
) | env GIT_SEND_EMAIL_NOTTY=1 git send-email \
--smtp-server="$(pwd)/fake.sendmail" \
$patches 2>errors &&
- ! grep "^In-Reply-To: < *>" msgtxt
+ ! grep "^In-Reply-To: < *>" msgtxt1
+'
+
+test_expect_success 'setup fake editor' '
+ (echo "#!/bin/sh" &&
+ echo "echo fake edit >>\$1"
+ ) >fake-editor &&
+ chmod +x fake-editor
+'
+
+test_expect_success '--compose works' '
+ clean_fake_sendmail &&
+ echo y | \
+ GIT_EDITOR=$(pwd)/fake-editor \
+ GIT_SEND_EMAIL_NOTTY=1 \
+ git send-email \
+ --compose --subject foo \
+ --from="Example <nobody@example.com>" \
+ --to=nobody@example.com \
+ --smtp-server="$(pwd)/fake.sendmail" \
+ $patches \
+ 2>errors
+'
+
+test_expect_success 'first message is compose text' '
+ grep "^fake edit" msgtxt1
+'
+
+test_expect_success 'second message is patch' '
+ grep "Subject:.*Second" msgtxt2
'
test_done
diff --git a/thread-utils.c b/thread-utils.c
new file mode 100644
index 000000000..55e7e2904
--- /dev/null
+++ b/thread-utils.c
@@ -0,0 +1,48 @@
+#include "cache.h"
+
+#ifdef _WIN32
+# define WIN32_LEAN_AND_MEAN
+# include <windows.h>
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+# include <sys/pstat.h>
+#endif
+
+/*
+ * By doing this in two steps we can at least get
+ * the function to be somewhat coherent, even
+ * with this disgusting nest of #ifdefs.
+ */
+#ifndef _SC_NPROCESSORS_ONLN
+# ifdef _SC_NPROC_ONLN
+# define _SC_NPROCESSORS_ONLN _SC_NPROC_ONLN
+# elif defined _SC_CRAY_NCPU
+# define _SC_NPROCESSORS_ONLN _SC_CRAY_NCPU
+# endif
+#endif
+
+int online_cpus(void)
+{
+#ifdef _SC_NPROCESSORS_ONLN
+ long ncpus;
+#endif
+
+#ifdef _WIN32
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+
+ if ((int)info.dwNumberOfProcessors > 0)
+ return (int)info.dwNumberOfProcessors;
+#elif defined(hpux) || defined(__hpux) || defined(_hpux)
+ struct pst_dynamic psd;
+
+ if (!pstat_getdynamic(&psd, sizeof(psd), (size_t)1, 0))
+ return (int)psd.psd_proc_cnt;
+#endif
+
+#ifdef _SC_NPROCESSORS_ONLN
+ if ((ncpus = (long)sysconf(_SC_NPROCESSORS_ONLN)) > 0)
+ return (int)ncpus;
+#endif
+
+ return 1;
+}
diff --git a/thread-utils.h b/thread-utils.h
new file mode 100644
index 000000000..cce4b77bd
--- /dev/null
+++ b/thread-utils.h
@@ -0,0 +1,6 @@
+#ifndef THREAD_COMPAT_H
+#define THREAD_COMPAT_H
+
+extern int online_cpus(void);
+
+#endif /* THREAD_COMPAT_H */
diff --git a/unpack-trees.c b/unpack-trees.c
index ec558f900..3e448d897 100644
--- a/unpack-trees.c
+++ b/unpack-trees.c
@@ -85,6 +85,7 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
int any_dirs = 0;
char *cache_name;
int ce_stage;
+ int skip_entry = 0;
/* Find the first name in the input. */
@@ -122,13 +123,13 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
#if DBRT_DEBUG > 1
if (first)
- printf("index %s\n", first);
+ fprintf(stderr, "index %s\n", first);
#endif
for (i = 0; i < len; i++) {
if (!posns[i] || posns[i] == df_conflict_list)
continue;
#if DBRT_DEBUG > 1
- printf("%d %s\n", i + 1, posns[i]->name);
+ fprintf(stderr, "%d %s\n", i + 1, posns[i]->name);
#endif
if (!first || entcmp(first, firstdir,
posns[i]->name,
@@ -153,6 +154,8 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
any_files = 1;
src[0] = active_cache[o->pos];
remove = o->pos;
+ if (o->skip_unmerged && ce_stage(src[0]))
+ skip_entry = 1;
}
for (i = 0; i < len; i++) {
@@ -181,6 +184,12 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
continue;
}
+ if (skip_entry) {
+ subposns[i] = df_conflict_list;
+ posns[i] = posns[i]->next;
+ continue;
+ }
+
if (!o->merge)
ce_stage = 0;
else if (i + 1 < o->head_idx)
@@ -205,23 +214,31 @@ static int unpack_trees_rec(struct tree_entry_list **posns, int len,
posns[i] = posns[i]->next;
}
if (any_files) {
- if (o->merge) {
+ if (skip_entry) {
+ o->pos++;
+ while (o->pos < active_nr &&
+ !strcmp(active_cache[o->pos]->name,
+ src[0]->name))
+ o->pos++;
+ } else if (o->merge) {
int ret;
#if DBRT_DEBUG > 1
- printf("%s:\n", first);
+ fprintf(stderr, "%s:\n", first);
for (i = 0; i < src_size; i++) {
- printf(" %d ", i);
+ fprintf(stderr, " %d ", i);
if (src[i])
- printf("%s\n", sha1_to_hex(src[i]->sha1));
+ fprintf(stderr, "%06x %s\n", src[i]->ce_mode, sha1_to_hex(src[i]->sha1));
else
- printf("\n");
+ fprintf(stderr, "\n");
}
#endif
ret = o->fn(src, o, remove);
+ if (ret < 0)
+ return ret;
#if DBRT_DEBUG > 1
- printf("Added %d entries\n", ret);
+ fprintf(stderr, "Added %d entries\n", ret);
#endif
o->pos += ret;
} else {
@@ -286,34 +303,36 @@ static void unlink_entry(char *name, char *last_symlink)
}
static struct checkout state;
-static void check_updates(struct cache_entry **src, int nr,
- struct unpack_trees_options *o)
+static void check_updates(struct unpack_trees_options *o)
{
unsigned cnt = 0, total = 0;
struct progress *progress = NULL;
char last_symlink[PATH_MAX];
+ int i;
if (o->update && o->verbose_update) {
- for (total = cnt = 0; cnt < nr; cnt++) {
- struct cache_entry *ce = src[cnt];
+ for (total = cnt = 0; cnt < active_nr; cnt++) {
+ struct cache_entry *ce = active_cache[cnt];
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
total++;
}
progress = start_progress_delay("Checking out files",
- total, 50, 2);
+ total, 50, 1);
cnt = 0;
}
*last_symlink = '\0';
- while (nr--) {
- struct cache_entry *ce = *src++;
+ for (i = 0; i < active_nr; i++) {
+ struct cache_entry *ce = active_cache[i];
if (ce->ce_flags & (CE_UPDATE | CE_REMOVE))
display_progress(progress, ++cnt);
if (ce->ce_flags & CE_REMOVE) {
if (o->update)
unlink_entry(ce->name, last_symlink);
+ remove_cache_entry_at(i);
+ i--;
continue;
}
if (ce->ce_flags & CE_UPDATE) {
@@ -354,23 +373,34 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options
posns[i] = create_tree_entry_list(t+i);
if (unpack_trees_rec(posns, len, o->prefix ? o->prefix : "",
- o, &df_conflict_list))
+ o, &df_conflict_list)) {
+ if (o->gently) {
+ discard_cache();
+ read_cache();
+ }
return -1;
+ }
}
- if (o->trivial_merges_only && o->nontrivial_merge)
- die("Merge requires file-level merging");
+ if (o->trivial_merges_only && o->nontrivial_merge) {
+ if (o->gently) {
+ discard_cache();
+ read_cache();
+ }
+ return o->gently ? -1 :
+ error("Merge requires file-level merging");
+ }
- check_updates(active_cache, active_nr, o);
+ check_updates(o);
return 0;
}
/* Here come the merge functions */
-static void reject_merge(struct cache_entry *ce)
+static int reject_merge(struct cache_entry *ce)
{
- die("Entry '%s' would be overwritten by merge. Cannot merge.",
- ce->name);
+ return error("Entry '%s' would be overwritten by merge. Cannot merge.",
+ ce->name);
}
static int same(struct cache_entry *a, struct cache_entry *b)
@@ -388,18 +418,18 @@ static int same(struct cache_entry *a, struct cache_entry *b)
* When a CE gets turned into an unmerged entry, we
* want it to be up-to-date
*/
-static void verify_uptodate(struct cache_entry *ce,
+static int verify_uptodate(struct cache_entry *ce,
struct unpack_trees_options *o)
{
struct stat st;
if (o->index_only || o->reset)
- return;
+ return 0;
if (!lstat(ce->name, &st)) {
unsigned changed = ce_match_stat(ce, &st, CE_MATCH_IGNORE_VALID);
if (!changed)
- return;
+ return 0;
/*
* NEEDSWORK: the current default policy is to allow
* submodule to be out of sync wrt the supermodule
@@ -408,12 +438,13 @@ static void verify_uptodate(struct cache_entry *ce,
* checked out.
*/
if (S_ISGITLINK(ce->ce_mode))
- return;
+ return 0;
errno = 0;
}
if (errno == ENOENT)
- return;
- die("Entry '%s' not uptodate. Cannot merge.", ce->name);
+ return 0;
+ return o->gently ? -1 :
+ error("Entry '%s' not uptodate. Cannot merge.", ce->name);
}
static void invalidate_ce_path(struct cache_entry *ce)
@@ -479,7 +510,8 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
* ce->name is an entry in the subdirectory.
*/
if (!ce_stage(ce)) {
- verify_uptodate(ce, o);
+ if (verify_uptodate(ce, o))
+ return -1;
ce->ce_flags |= CE_REMOVE;
}
cnt++;
@@ -498,8 +530,9 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
d.exclude_per_dir = o->dir->exclude_per_dir;
i = read_directory(&d, ce->name, pathbuf, namelen+1, NULL);
if (i)
- die("Updating '%s' would lose untracked files in it",
- ce->name);
+ return o->gently ? -1 :
+ error("Updating '%s' would lose untracked files in it",
+ ce->name);
free(pathbuf);
return cnt;
}
@@ -508,16 +541,16 @@ static int verify_clean_subdirectory(struct cache_entry *ce, const char *action,
* We do not want to remove or overwrite a working tree file that
* is not tracked, unless it is ignored.
*/
-static void verify_absent(struct cache_entry *ce, const char *action,
- struct unpack_trees_options *o)
+static int verify_absent(struct cache_entry *ce, const char *action,
+ struct unpack_trees_options *o)
{
struct stat st;
if (o->index_only || o->reset || !o->update)
- return;
+ return 0;
if (has_symlink_leading_path(ce->name, NULL))
- return;
+ return 0;
if (!lstat(ce->name, &st)) {
int cnt;
@@ -528,7 +561,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
* ce->name is explicitly excluded, so it is Ok to
* overwrite it.
*/
- return;
+ return 0;
if (S_ISDIR(st.st_mode)) {
/*
* We are checking out path "foo" and
@@ -557,7 +590,7 @@ static void verify_absent(struct cache_entry *ce, const char *action,
* deleted entries here.
*/
o->pos += cnt;
- return;
+ return 0;
}
/*
@@ -569,12 +602,14 @@ static void verify_absent(struct cache_entry *ce, const char *action,
if (0 <= cnt) {
struct cache_entry *ce = active_cache[cnt];
if (ce->ce_flags & CE_REMOVE)
- return;
+ return 0;
}
- die("Untracked working tree file '%s' "
- "would be %s by merge.", ce->name, action);
+ return o->gently ? -1 :
+ error("Untracked working tree file '%s' "
+ "would be %s by merge.", ce->name, action);
}
+ return 0;
}
static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
@@ -590,14 +625,16 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
* a match.
*/
if (same(old, merge)) {
- memcpy(merge, old, offsetof(struct cache_entry, name));
+ copy_cache_entry(merge, old);
} else {
- verify_uptodate(old, o);
+ if (verify_uptodate(old, o))
+ return -1;
invalidate_ce_path(old);
}
}
else {
- verify_absent(merge, "overwritten", o);
+ if (verify_absent(merge, "overwritten", o))
+ return -1;
invalidate_ce_path(merge);
}
@@ -609,10 +646,12 @@ static int merged_entry(struct cache_entry *merge, struct cache_entry *old,
static int deleted_entry(struct cache_entry *ce, struct cache_entry *old,
struct unpack_trees_options *o)
{
- if (old)
- verify_uptodate(old, o);
- else
- verify_absent(ce, "removed", o);
+ if (old) {
+ if (verify_uptodate(old, o))
+ return -1;
+ } else
+ if (verify_absent(ce, "removed", o))
+ return -1;
ce->ce_flags |= CE_REMOVE;
add_cache_entry(ce, ADD_CACHE_OK_TO_ADD|ADD_CACHE_OK_TO_REPLACE);
invalidate_ce_path(ce);
@@ -700,16 +739,15 @@ int threeway_merge(struct cache_entry **stages,
/* #14, #14ALT, #2ALT */
if (remote && !df_conflict_head && head_match && !remote_match) {
if (index && !same(index, remote) && !same(index, head))
- reject_merge(index);
+ return o->gently ? -1 : reject_merge(index);
return merged_entry(remote, index, o);
}
/*
* If we have an entry in the index cache, then we want to
* make sure that it matches head.
*/
- if (index && !same(index, head)) {
- reject_merge(index);
- }
+ if (index && !same(index, head))
+ return o->gently ? -1 : reject_merge(index);
if (head) {
/* #5ALT, #15 */
@@ -759,8 +797,10 @@ int threeway_merge(struct cache_entry **stages,
remove_entry(remove);
if (index)
return deleted_entry(index, index, o);
- else if (ce && !head_deleted)
- verify_absent(ce, "removed", o);
+ else if (ce && !head_deleted) {
+ if (verify_absent(ce, "removed", o))
+ return -1;
+ }
return 0;
}
/*
@@ -776,7 +816,8 @@ int threeway_merge(struct cache_entry **stages,
* conflict resolution files.
*/
if (index) {
- verify_uptodate(index, o);
+ if (verify_uptodate(index, o))
+ return -1;
}
remove_entry(remove);
@@ -856,11 +897,11 @@ int twoway_merge(struct cache_entry **src,
/* all other failures */
remove_entry(remove);
if (oldtree)
- reject_merge(oldtree);
+ return o->gently ? -1 : reject_merge(oldtree);
if (current)
- reject_merge(current);
+ return o->gently ? -1 : reject_merge(current);
if (newtree)
- reject_merge(newtree);
+ return o->gently ? -1 : reject_merge(newtree);
return -1;
}
}
@@ -887,7 +928,8 @@ int bind_merge(struct cache_entry **src,
return error("Cannot do a bind merge of %d trees\n",
o->merge_size);
if (a && old)
- die("Entry '%s' overlaps. Cannot bind.", a->name);
+ return o->gently ? -1 :
+ error("Entry '%s' overlaps. Cannot bind.", a->name);
if (!a)
return keep_entry(old, o);
else
diff --git a/unpack-trees.h b/unpack-trees.h
index 197a0044a..a2df544d0 100644
--- a/unpack-trees.h
+++ b/unpack-trees.h
@@ -16,6 +16,8 @@ struct unpack_trees_options {
int trivial_merges_only;
int verbose_update;
int aggressive;
+ int skip_unmerged;
+ int gently;
const char *prefix;
int pos;
struct dir_struct *dir;
diff --git a/ws.c b/ws.c
index d09b9df89..ba7e834ca 100644
--- a/ws.c
+++ b/ws.c
@@ -14,6 +14,7 @@ static struct whitespace_rule {
{ "trailing-space", WS_TRAILING_SPACE },
{ "space-before-tab", WS_SPACE_BEFORE_TAB },
{ "indent-with-non-tab", WS_INDENT_WITH_NON_TAB },
+ { "cr-at-eol", WS_CR_AT_EOL },
};
unsigned parse_whitespace_rule(const char *string)
@@ -124,6 +125,7 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
int written = 0;
int trailing_whitespace = -1;
int trailing_newline = 0;
+ int trailing_carriage_return = 0;
int i;
/* Logic is simpler if we temporarily ignore the trailing newline. */
@@ -131,6 +133,11 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
trailing_newline = 1;
len--;
}
+ if ((ws_rule & WS_CR_AT_EOL) &&
+ len > 0 && line[len - 1] == '\r') {
+ trailing_carriage_return = 1;
+ len--;
+ }
/* Check for trailing whitespace. */
if (ws_rule & WS_TRAILING_SPACE) {
@@ -176,8 +183,10 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
}
if (stream) {
- /* Now the rest of the line starts at written.
- * The non-highlighted part ends at trailing_whitespace. */
+ /*
+ * Now the rest of the line starts at "written".
+ * The non-highlighted part ends at "trailing_whitespace".
+ */
if (trailing_whitespace == -1)
trailing_whitespace = len;
@@ -196,8 +205,114 @@ unsigned check_and_emit_line(const char *line, int len, unsigned ws_rule,
len - trailing_whitespace, 1, stream);
fputs(reset, stream);
}
+ if (trailing_carriage_return)
+ fputc('\r', stream);
if (trailing_newline)
fputc('\n', stream);
}
return result;
}
+
+/* Copy the line to the buffer while fixing whitespaces */
+int ws_fix_copy(char *dst, const char *src, int len, unsigned ws_rule, int *error_count)
+{
+ /*
+ * len is number of bytes to be copied from src, starting
+ * at src. Typically src[len-1] is '\n', unless this is
+ * the incomplete last line.
+ */
+ int i;
+ int add_nl_to_tail = 0;
+ int add_cr_to_tail = 0;
+ int fixed = 0;
+ int last_tab_in_indent = -1;
+ int last_space_in_indent = -1;
+ int need_fix_leading_space = 0;
+ char *buf;
+
+ /*
+ * Strip trailing whitespace
+ */
+ if ((ws_rule & WS_TRAILING_SPACE) &&
+ (2 <= len && isspace(src[len-2]))) {
+ if (src[len - 1] == '\n') {
+ add_nl_to_tail = 1;
+ len--;
+ if (1 < len && src[len - 1] == '\r') {
+ add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL);
+ len--;
+ }
+ }
+ if (0 < len && isspace(src[len - 1])) {
+ while (0 < len && isspace(src[len-1]))
+ len--;
+ fixed = 1;
+ }
+ }
+
+ /*
+ * Check leading whitespaces (indent)
+ */
+ for (i = 0; i < len; i++) {
+ char ch = src[i];
+ if (ch == '\t') {
+ last_tab_in_indent = i;
+ if ((ws_rule & WS_SPACE_BEFORE_TAB) &&
+ 0 <= last_space_in_indent)
+ need_fix_leading_space = 1;
+ } else if (ch == ' ') {
+ last_space_in_indent = i;
+ if ((ws_rule & WS_INDENT_WITH_NON_TAB) &&
+ 8 <= i - last_tab_in_indent)
+ need_fix_leading_space = 1;
+ } else
+ break;
+ }
+
+ buf = dst;
+ if (need_fix_leading_space) {
+ /* Process indent ourselves */
+ int consecutive_spaces = 0;
+ int last = last_tab_in_indent + 1;
+
+ if (ws_rule & WS_INDENT_WITH_NON_TAB) {
+ /* have "last" point at one past the indent */
+ if (last_tab_in_indent < last_space_in_indent)
+ last = last_space_in_indent + 1;
+ else
+ last = last_tab_in_indent + 1;
+ }
+
+ /*
+ * between src[0..last-1], strip the funny spaces,
+ * updating them to tab as needed.
+ */
+ for (i = 0; i < last; i++) {
+ char ch = src[i];
+ if (ch != ' ') {
+ consecutive_spaces = 0;
+ *dst++ = ch;
+ } else {
+ consecutive_spaces++;
+ if (consecutive_spaces == 8) {
+ *dst++ = '\t';
+ consecutive_spaces = 0;
+ }
+ }
+ }
+ while (0 < consecutive_spaces--)
+ *dst++ = ' ';
+ len -= last;
+ src += last;
+ fixed = 1;
+ }
+
+ memcpy(dst, src, len);
+ if (add_cr_to_tail)
+ dst[len++] = '\r';
+ if (add_nl_to_tail)
+ dst[len++] = '\n';
+ if (fixed && error_count)
+ (*error_count)++;
+ return dst + len - buf;
+}
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h
index c00ddaa6e..413082e1f 100644
--- a/xdiff/xdiff.h
+++ b/xdiff/xdiff.h
@@ -53,6 +53,7 @@ extern "C" {
#define XDL_MERGE_MINIMAL 0
#define XDL_MERGE_EAGER 1
#define XDL_MERGE_ZEALOUS 2
+#define XDL_MERGE_ZEALOUS_ALNUM 3
typedef struct s_mmfile {
char *ptr;
diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c
index b83b3348c..82b3573e7 100644
--- a/xdiff/xmerge.c
+++ b/xdiff/xmerge.c
@@ -248,10 +248,76 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m,
return 0;
}
+static int line_contains_alnum(const char *ptr, long size)
+{
+ while (size--)
+ if (isalnum(*(ptr++)))
+ return 1;
+ return 0;
+}
+
+static int lines_contain_alnum(xdfenv_t *xe, int i, int chg)
+{
+ for (; chg; chg--, i++)
+ if (line_contains_alnum(xe->xdf2.recs[i]->ptr,
+ xe->xdf2.recs[i]->size))
+ return 1;
+ return 0;
+}
+
+/*
+ * This function merges m and m->next, marking everything between those hunks
+ * as conflicting, too.
+ */
+static void xdl_merge_two_conflicts(xdmerge_t *m)
+{
+ xdmerge_t *next_m = m->next;
+ m->chg1 = next_m->i1 + next_m->chg1 - m->i1;
+ m->chg2 = next_m->i2 + next_m->chg2 - m->i2;
+ m->next = next_m->next;
+ free(next_m);
+}
+
+/*
+ * If there are less than 3 non-conflicting lines between conflicts,
+ * it appears simpler -- because it takes up less (or as many) lines --
+ * if the lines are moved into the conflicts.
+ */
+static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m,
+ int simplify_if_no_alnum)
+{
+ int result = 0;
+
+ if (!m)
+ return result;
+ for (;;) {
+ xdmerge_t *next_m = m->next;
+ int begin, end;
+
+ if (!next_m)
+ return result;
+
+ begin = m->i1 + m->chg1;
+ end = next_m->i1;
+
+ if (m->mode != 0 || next_m->mode != 0 ||
+ (end - begin > 3 &&
+ (!simplify_if_no_alnum ||
+ lines_contain_alnum(xe1, begin, end - begin)))) {
+ m = next_m;
+ } else {
+ result++;
+ xdl_merge_two_conflicts(m);
+ }
+ }
+}
+
/*
* level == 0: mark all overlapping changes as conflict
* level == 1: mark overlapping changes as conflict only if not identical
* level == 2: analyze non-identical changes for minimal conflict set
+ * level == 3: analyze non-identical changes for minimal conflict set, but
+ * treat hunks not containing any letter or number as conflicting
*
* returns < 0 on error, == 0 for no conflicts, else number of conflicts
*/
@@ -355,7 +421,9 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1,
if (!changes)
changes = c;
/* refine conflicts */
- if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) {
+ if (level > 1 &&
+ (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 ||
+ xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) {
xdl_cleanup_merge(changes);
return -1;
}