Merge commit '74359821' into js/reflog-delete
authorJunio C Hamano <gitster@pobox.com>
Mon, 3 Mar 2008 09:20:19 +0000 (01:20 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 3 Mar 2008 09:20:19 +0000 (01:20 -0800)
* commit '74359821': (128 commits)
  tests: introduce test_must_fail
  Fix builtin checkout crashing when given an invalid path
  templates/Makefile: don't depend on local umask setting
  Correct name of diff_flush() in API documentation
  Start preparing for 1.5.4.4
  format-patch: remove a leftover debugging message
  completion: support format-patch's --cover-letter option
  Eliminate confusing "won't bisect on seeked tree" failure
  builtin-reflog.c: don't install new reflog on write failure
  send-email: fix In-Reply-To regression
  git-svn: Don't prompt for client cert password everytime.
  git.el: Do not display empty directories.
  Fix 'git cvsexportcommit -w $cvsdir ...' when used with relative $GIT_DIR
  Add testcase for 'git cvsexportcommit -w $cvsdir ...' with relative $GIT_DIR
  Prompt to continue when editing during rebase --interactive
  Documentation/git svn log: add a note about timezones.
  git-p4: Support usage of perforce client spec
  git-p4: git-p4 submit cleanups.
  git-p4: Removed git-p4 submit --direct.
  git-p4: Clean up git-p4 submit's log message handling.
  ...

125 files changed:
Documentation/RelNotes-1.5.4.3.txt [new file with mode: 0644]
Documentation/RelNotes-1.5.4.4.txt [new file with mode: 0644]
Documentation/config.txt
Documentation/diff-options.txt
Documentation/git-am.txt
Documentation/git-branch.txt
Documentation/git-bundle.txt
Documentation/git-checkout.txt
Documentation/git-describe.txt
Documentation/git-filter-branch.txt
Documentation/git-format-patch.txt
Documentation/git-pack-objects.txt
Documentation/git-rev-list.txt
Documentation/git-svn.txt
Documentation/git.txt
Documentation/rev-list-options.txt
Documentation/technical/api-diff.txt
Documentation/urls.txt
GIT-VERSION-GEN
Makefile
alias.c [new file with mode: 0644]
branch.c [new file with mode: 0644]
branch.h [new file with mode: 0644]
builtin-apply.c
builtin-blame.c
builtin-branch.c
builtin-checkout.c [new file with mode: 0644]
builtin-commit.c
builtin-describe.c
builtin-diff.c
builtin-fast-export.c
builtin-fetch-pack.c
builtin-for-each-ref.c
builtin-http-fetch.c
builtin-init-db.c
builtin-log.c
builtin-merge-file.c
builtin-merge-recursive.c [moved from merge-recursive.c with 98% similarity]
builtin-pack-objects.c
builtin-push.c
builtin-read-tree.c
builtin-rerere.c
builtin-reset.c
builtin-rev-parse.c
builtin-revert.c
builtin-send-pack.c
builtin-shortlog.c
builtin-tag.c
builtin-verify-tag.c
builtin.h
bundle.c
cache.h
commit.h
config.c
connect.c
contrib/completion/git-completion.bash
contrib/emacs/git.el
contrib/examples/git-checkout.sh [moved from git-checkout.sh with 97% similarity]
contrib/fast-import/git-p4
copy.c
date.c
diff-lib.c
diff.c
diff.h
dir.c
environment.c
git-bisect.sh
git-gui/Makefile
git-gui/git-gui.sh
git-gui/lib/choose_repository.tcl
git-gui/lib/error.tcl
git-pull.sh
git-rebase--interactive.sh
git-svn.perl
git.c
gitweb/gitweb.perl
hash-object.c
help.c
http-push.c
imap-send.c
interpolate.c
log-tree.c
log-tree.h
merge-recursive.h [new file with mode: 0644]
pretty.c
read-cache.c
receive-pack.c
refs.c
remote.c
remote.h
revision.c
revision.h
run-command.c
run-command.h
setup.c
sha1_name.c
shortlog.h [new file with mode: 0644]
t/t0050-filesystem.sh [new file with mode: 0755]
t/t1502-rev-parse-parseopt.sh [new file with mode: 0755]
t/t2008-checkout-subdir.sh
t/t3200-branch.sh
t/t4013-diff-various.sh
t/t4013/diff.format-patch_--stdout_--cover-letter_-n_initial..master^ [new file with mode: 0644]
t/t4014-format-patch.sh
t/t4019-diff-wserror.sh
t/t4105-apply-fuzz.sh [new file with mode: 0755]
t/t4125-apply-ws-fuzz.sh [new file with mode: 0755]
t/t5303-hash-object.sh [new file with mode: 0755]
t/t5505-remote.sh
t/t5516-fetch-push.sh
t/t6023-merge-file.sh
t/t6029-merge-subtree.sh [new file with mode: 0755]
t/t6030-bisect-porcelain.sh
t/t7201-co.sh
t/t9001-send-email.sh
t/test-lib.sh
templates/Makefile
thread-utils.c [new file with mode: 0644]
thread-utils.h [new file with mode: 0644]
unpack-trees.c
unpack-trees.h
ws.c
xdiff-interface.c
xdiff/xdiff.h
xdiff/xmerge.c

diff --git a/Documentation/RelNotes-1.5.4.3.txt b/Documentation/RelNotes-1.5.4.3.txt
new file mode 100644 (file)
index 0000000..b0fc67f
--- /dev/null
@@ -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/RelNotes-1.5.4.4.txt b/Documentation/RelNotes-1.5.4.4.txt
new file mode 100644 (file)
index 0000000..5bfdb35
--- /dev/null
@@ -0,0 +1,26 @@
+GIT v1.5.4.4 Release Notes
+==========================
+
+Fixes since v1.5.4.3
+--------------------
+
+ * "git cvsexportcommit -w $cvsdir" misbehaved when GIT_DIR is set to a
+   relative directory.
+
+ * "git http-push" had an invalid memory access that could lead it to
+   segfault.
+
+ * When "git rebase -i" gave control back to the user for a commit that is
+   marked to be edited, it just said "modify it with commit --amend",
+   without saying what to do to continue after modifying it.  Give an
+   explicit instruction to run "rebase --continue" to be more helpful.
+
+ * "git send-email" in 1.5.4.3 issued a bogus empty In-Reply-To: header.
+
+Also included are a handful documentation updates.
+
+---
+exec >/var/tmp/1
+echo O=$(git describe maint)
+O=v1.5.4.3
+git shortlog --no-merges $O..maint
index 7b67671..4027726 100644 (file)
@@ -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
index 8d35cbd..8dc5b00 100644 (file)
@@ -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.
 
index 2ffba21..e640fc7 100644 (file)
@@ -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.
 
 
index 7e8874a..6f07a17 100644 (file)
@@ -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.
index 72f080a..505ac05 100644 (file)
@@ -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
index b4cfa04..4014e72 100644 (file)
@@ -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
index 1c3dfb4..fbb40a2 100644 (file)
@@ -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
index e22dfa5..543a1cf 100644 (file)
@@ -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
index 651efe6..b5207b7 100644 (file)
@@ -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
index 8353be1..5c1bd3b 100644 (file)
@@ -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
index 5b96eab..a8d489f 100644 (file)
@@ -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 ]
index 340f1be..bec9acc 100644 (file)
@@ -159,6 +159,10 @@ New features:
        our version of --pretty=oneline
 --
 +
+NOTE: SVN itself only stores times in UTC and nothing else. The regular svn
+client converts the UTC time to the local time (or based on the TZ=
+environment). This command has the same behaviour.
++
 Any other arguments are passed directly to `git log'
 
 'blame'::
index d57bed6..741ae0e 100644 (file)
@@ -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].
index a8138e2..259072c 100644 (file)
@@ -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.
index 83b007e..20b0241 100644 (file)
@@ -39,7 +39,7 @@ Calling sequence
 * Once you finish feeding the pairs of files, call `diffcore_std()`.
   This will tell the diffcore library to go ahead and do its work.
 
-* Calling `diffcore_flush()` will produce the output.
+* Calling `diff_flush()` will produce the output.
 
 
 Data structures
index 81ac17f..fa34c67 100644 (file)
@@ -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".
+
index 1ad324e..6ddf04d 100755 (executable)
@@ -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');
index d33a556..a5b6eeb 100644 (file)
--- 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 (file)
index 0000000..116cac8
--- /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 (file)
index 0000000..daf862e
--- /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 (file)
index 0000000..9f0c2a2
--- /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
index 6a88ff0..a3f075d 100644 (file)
@@ -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");
index 59d7237..bfd562d 100644 (file)
@@ -123,8 +123,7 @@ static inline struct origin *origin_incref(struct origin *o)
 static void origin_decref(struct origin *o)
 {
        if (o && --o->refcnt <= 0) {
-               if (o->file.ptr)
-                       free(o->file.ptr);
+               free(o->file.ptr);
                free(o);
        }
 }
index 9edf2eb..5bc4526 100644 (file)
@@ -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);
 }
 
@@ -126,8 +121,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                        continue;
                }
 
-               if (name)
-                       free(name);
+               free(name);
 
                name = xstrdup(mkpath(fmt, argv[i]));
                if (!resolve_ref(name, sha1, 1, NULL)) {
@@ -172,8 +166,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds)
                }
        }
 
-       if (name)
-               free(name);
+       free(name);
 
        return(ret);
 }
@@ -359,141 +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));
-
-       if (real_ref)
-               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];
@@ -554,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),
@@ -592,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);
@@ -618,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 (file)
index 0000000..9579ff4
--- /dev/null
@@ -0,0 +1,573 @@
+#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);
+
+               if (!pathspec)
+                       die("invalid path specification");
+
+               /* 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);
+}
index 065e1f7..f49c22e 100644 (file)
@@ -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)
index 3428483..05e309f 100644 (file)
@@ -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;
 
index 8f53f52..444ff2f 100644 (file)
@@ -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);
 
index f741df5..e1c5630 100755 (executable)
@@ -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);
                }
        }
@@ -196,8 +196,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev)
                          ? strlen(reencoded) : message
                          ? strlen(message) : 0),
               reencoded ? reencoded : message ? message : "");
-       if (reencoded)
-               free(reencoded);
+       free(reencoded);
 
        for (i = 0, p = commit->parents; p; p = p->next) {
                int mark = get_object_mark(&p->item->object);
index f401352..5ea48ca 100644 (file)
@@ -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]);
index f36a43c..07d9c57 100644 (file)
@@ -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;
index 7f450c6..299093f 100644 (file)
@@ -80,8 +80,7 @@ int cmd_http_fetch(int argc, const char **argv, const char *prefix)
 
        walker_free(walker);
 
-       if (rewritten_url)
-               free(rewritten_url);
+       free(rewritten_url);
 
        return rc;
 }
index 5d7cdda..79eaf8d 100644 (file)
@@ -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)
index c67d63c..836b61e 100644 (file)
@@ -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,14 @@ 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) {
+                       boundary_count++;
+                       origin = (boundary_count == 1) ? commit : NULL;
+                       continue;
+               }
+
                /* ignore merges */
                if (commit->parents && commit->parents->next)
                        continue;
@@ -820,29 +982,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;
index 58deb62..adce6d4 100644 (file)
@@ -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);
similarity index 98%
rename from merge-recursive.c
rename to builtin-merge-recursive.c
index 55ef76f..6fe4102 100644 (file)
@@ -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) ||
index d2bb12e..1bba6e6 100644 (file)
@@ -16,6 +16,7 @@
 #include "progress.h"
 
 #ifdef THREADED_DELTA_SEARCH
+#include "thread-utils.h"
 #include <pthread.h>
 #endif
 
@@ -1428,8 +1429,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
         * accounting lock.  Compiler will optimize the strangeness
         * away when THREADED_DELTA_SEARCH is not defined.
         */
-       if (trg_entry->delta_data)
-               free(trg_entry->delta_data);
+       free(trg_entry->delta_data);
        cache_lock();
        if (trg_entry->delta_data) {
                delta_cache_size -= trg_entry->delta_size;
@@ -1852,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;
@@ -2122,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
@@ -2235,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)
index 9f727c0..b68c681 100644 (file)
@@ -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);
        }
 }
index 726fb0b..0138f5a 100644 (file)
@@ -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,
index b0c17bd..c607aad 100644 (file)
@@ -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,
index 7ee811f..af0037e 100644 (file)
@@ -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);
 
index b9af1a5..90dbb9d 100644 (file)
@@ -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;
                }
 
index e219859..b6dee6a 100644 (file)
@@ -397,8 +397,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
                else
                        return execl_git_cmd("commit", "-n", "-F", defmsg, NULL);
        }
-       if (reencoded_message)
-               free(reencoded_message);
+       free(reencoded_message);
 
        return 0;
 }
index 8afb1d0..b0cfae8 100644 (file)
@@ -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;
 }
index 0055a57..af31aba 100644 (file)
@@ -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);
 }
index 716b4ff..28c36fd 100644 (file)
@@ -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");
index cc4c55d..f3ef11f 100644 (file)
@@ -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);
index 3d1628c..674c8a1 100644 (file)
--- 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);
index bd12ec8..0ba5df1 100644 (file)
--- 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 e1000bc..f16d341 100644 (file)
--- 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 */
index 10e2b5d..80d65b9 100644 (file)
--- 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.
index cba2bcf..0624494 100644 (file)
--- 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;
index 5ac3572..d12b105 100644 (file)
--- a/connect.c
+++ b/connect.c
@@ -68,8 +68,7 @@ struct ref **get_remote_heads(int in, struct ref **list,
 
                name_len = strlen(name);
                if (len != name_len + 41) {
-                       if (server_capabilities)
-                               free(server_capabilities);
+                       free(server_capabilities);
                        server_capabilities = xstrdup(name + name_len + 1);
                }
 
index 4ea727b..8f70e1e 100755 (executable)
@@ -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
 
@@ -645,6 +648,7 @@ _git_format_patch ()
                        --in-reply-to=
                        --full-index --binary
                        --not --all
+                       --cover-letter
                        "
                return
                ;;
index f69b697..cc21e9c 100644 (file)
@@ -728,7 +728,7 @@ Return the list of files that haven't been handled."
 (defun git-run-ls-files-with-excludes (status files default-state &rest options)
   "Run git-ls-files on FILES with appropriate --exclude-from options."
   (let ((exclude-files (git-get-exclude-files)))
-    (apply #'git-run-ls-files status files default-state "--directory"
+    (apply #'git-run-ls-files status files default-state "--directory" "--no-empty-directory"
            (concat "--exclude-per-directory=" git-per-dir-ignore-file)
            (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
 
similarity index 97%
rename from git-checkout.sh
rename to contrib/examples/git-checkout.sh
index bd74d70..1a7689a 100755 (executable)
@@ -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.
index 781a0cb..be96600 100755 (executable)
@@ -464,71 +464,47 @@ class P4Submit(Command):
     def __init__(self):
         Command.__init__(self)
         self.options = [
-                optparse.make_option("--continue", action="store_false", dest="firstTime"),
                 optparse.make_option("--verbose", dest="verbose", action="store_true"),
                 optparse.make_option("--origin", dest="origin"),
-                optparse.make_option("--reset", action="store_true", dest="reset"),
-                optparse.make_option("--log-substitutions", dest="substFile"),
-                optparse.make_option("--direct", dest="directSubmit", action="store_true"),
                 optparse.make_option("-M", dest="detectRename", action="store_true"),
         ]
         self.description = "Submit changes from git to the perforce depot."
         self.usage += " [name of git branch to submit into perforce depot]"
-        self.firstTime = True
-        self.reset = False
         self.interactive = True
-        self.substFile = ""
-        self.firstTime = True
         self.origin = ""
-        self.directSubmit = False
         self.detectRename = False
         self.verbose = False
         self.isWindows = (platform.system() == "Windows")
 
-        self.logSubstitutions = {}
-        self.logSubstitutions["<enter description here>"] = "%log%"
-        self.logSubstitutions["\tDetails:"] = "\tDetails:  %log%"
-
     def check(self):
         if len(p4CmdList("opened ...")) > 0:
             die("You have files opened with perforce! Close them before starting the sync.")
 
-    def start(self):
-        if len(self.config) > 0 and not self.reset:
-            die("Cannot start sync. Previous sync config found at %s\n"
-                "If you want to start submitting again from scratch "
-                "maybe you want to call git-p4 submit --reset" % self.configFile)
-
-        commits = []
-        if self.directSubmit:
-            commits.append("0")
-        else:
-            for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
-                commits.append(line.strip())
-            commits.reverse()
-
-        self.config["commits"] = commits
-
+    # replaces everything between 'Description:' and the next P4 submit template field with the
+    # commit message
     def prepareLogMessage(self, template, message):
         result = ""
 
+        inDescriptionSection = False
+
         for line in template.split("\n"):
             if line.startswith("#"):
                 result += line + "\n"
                 continue
 
-            substituted = False
-            for key in self.logSubstitutions.keys():
-                if line.find(key) != -1:
-                    value = self.logSubstitutions[key]
-                    value = value.replace("%log%", message)
-                    if value != "@remove@":
-                        result += line.replace(key, value) + "\n"
-                    substituted = True
-                    break
+            if inDescriptionSection:
+                if line.startswith("Files:"):
+                    inDescriptionSection = False
+                else:
+                    continue
+            else:
+                if line.startswith("Description:"):
+                    inDescriptionSection = True
+                    line += "\n"
+                    for messageLine in message.split("\n"):
+                        line += "\t" + messageLine + "\n"
 
-            if not substituted:
-                result += line + "\n"
+            result += line + "\n"
 
         return result
 
@@ -557,13 +533,9 @@ class P4Submit(Command):
         return template
 
     def applyCommit(self, id):
-        if self.directSubmit:
-            print "Applying local change in working directory/index"
-            diff = self.diffStatus
-        else:
-            print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
-            diffOpts = ("", "-M")[self.detectRename]
-            diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
+        print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
+        diffOpts = ("", "-M")[self.detectRename]
+        diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
         filesToAdd = set()
         filesToDelete = set()
         editedFiles = set()
@@ -598,10 +570,7 @@ class P4Submit(Command):
             else:
                 die("unknown modifier %s for %s" % (modifier, path))
 
-        if self.directSubmit:
-            diffcmd = "cat \"%s\"" % self.diffFile
-        else:
-            diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
+        diffcmd = "git format-patch -k --stdout \"%s^\"..\"%s\"" % (id, id)
         patchcmd = diffcmd + " | git apply "
         tryPatchCmd = patchcmd + "--check -"
         applyPatchCmd = patchcmd + "--check --apply -"
@@ -649,13 +618,10 @@ class P4Submit(Command):
             mode = filesToChangeExecBit[f]
             setP4ExecBit(f, mode)
 
-        logMessage = ""
-        if not self.directSubmit:
-            logMessage = extractLogMessageFromGitCommit(id)
-            logMessage = logMessage.replace("\n", "\n\t")
-            if self.isWindows:
-                logMessage = logMessage.replace("\n", "\r\n")
-            logMessage = logMessage.strip()
+        logMessage = extractLogMessageFromGitCommit(id)
+        if self.isWindows:
+            logMessage = logMessage.replace("\n", "\r\n")
+        logMessage = logMessage.strip()
 
         template = self.prepareSubmitTemplate()
 
@@ -694,12 +660,6 @@ class P4Submit(Command):
             if self.isWindows:
                 submitTemplate = submitTemplate.replace("\r\n", "\n")
 
-            if self.directSubmit:
-                print "Submitting to git first"
-                os.chdir(self.oldWorkingDirectory)
-                write_pipe("git commit -a -F -", submitTemplate)
-                os.chdir(self.clientPath)
-
             write_pipe("p4 submit -i", submitTemplate)
         else:
             fileName = "submit.txt"
@@ -741,65 +701,33 @@ class P4Submit(Command):
         print "Perforce checkout for depot path %s located at %s" % (self.depotPath, self.clientPath)
         self.oldWorkingDirectory = os.getcwd()
 
-        if self.directSubmit:
-            self.diffStatus = read_pipe_lines("git diff -r --name-status HEAD")
-            if len(self.diffStatus) == 0:
-                print "No changes in working directory to submit."
-                return True
-            patch = read_pipe("git diff -p --binary --diff-filter=ACMRTUXB HEAD")
-            self.diffFile = self.gitdir + "/p4-git-diff"
-            f = open(self.diffFile, "wb")
-            f.write(patch)
-            f.close();
-
         os.chdir(self.clientPath)
         print "Syncronizing p4 checkout..."
         system("p4 sync ...")
 
-        if self.reset:
-            self.firstTime = True
-
-        if len(self.substFile) > 0:
-            for line in open(self.substFile, "r").readlines():
-                tokens = line.strip().split("=")
-                self.logSubstitutions[tokens[0]] = tokens[1]
-
         self.check()
-        self.configFile = self.gitdir + "/p4-git-sync.cfg"
-        self.config = shelve.open(self.configFile, writeback=True)
-
-        if self.firstTime:
-            self.start()
 
-        commits = self.config.get("commits", [])
+        commits = []
+        for line in read_pipe_lines("git rev-list --no-merges %s..%s" % (self.origin, self.master)):
+            commits.append(line.strip())
+        commits.reverse()
 
         while len(commits) > 0:
-            self.firstTime = False
             commit = commits[0]
             commits = commits[1:]
-            self.config["commits"] = commits
             self.applyCommit(commit)
             if not self.interactive:
                 break
 
-        self.config.close()
-
-        if self.directSubmit:
-            os.remove(self.diffFile)
-
         if len(commits) == 0:
-            if self.firstTime:
-                print "No changes found to apply between %s and current HEAD" % self.origin
-            else:
-                print "All changes applied!"
-                os.chdir(self.oldWorkingDirectory)
+            print "All changes applied!"
+            os.chdir(self.oldWorkingDirectory)
 
-                sync = P4Sync()
-                sync.run([])
+            sync = P4Sync()
+            sync.run([])
 
-                rebase = P4Rebase()
-                rebase.rebase()
-            os.remove(self.configFile)
+            rebase = P4Rebase()
+            rebase.rebase()
 
         return True
 
@@ -817,7 +745,9 @@ class P4Sync(Command):
                                      help="Import into refs/heads/ , not refs/remotes"),
                 optparse.make_option("--max-changes", dest="maxChanges"),
                 optparse.make_option("--keep-path", dest="keepRepoPath", action='store_true',
-                                     help="Keep entire BRANCH/DIR/SUBDIR prefix during import")
+                                     help="Keep entire BRANCH/DIR/SUBDIR prefix during import"),
+                optparse.make_option("--use-client-spec", dest="useClientSpec", action='store_true',
+                                     help="Only sync files that are included in the Perforce Client Spec")
         ]
         self.description = """Imports from Perforce into a git repository.\n
     example:
@@ -843,18 +773,27 @@ class P4Sync(Command):
         self.keepRepoPath = False
         self.depotPaths = None
         self.p4BranchesInGit = []
+        self.cloneExclude = []
+        self.useClientSpec = False
+        self.clientSpecDirs = []
 
         if gitConfig("git-p4.syncFromOrigin") == "false":
             self.syncWithOrigin = False
 
     def extractFilesFromCommit(self, commit):
+        self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
+                             for path in self.cloneExclude]
         files = []
         fnum = 0
         while commit.has_key("depotFile%s" % fnum):
             path =  commit["depotFile%s" % fnum]
 
-            found = [p for p in self.depotPaths
-                     if path.startswith (p)]
+            if [p for p in self.cloneExclude
+                if path.startswith (p)]:
+                found = False
+            else:
+                found = [p for p in self.depotPaths
+                         if path.startswith (p)]
             if not found:
                 fnum = fnum + 1
                 continue
@@ -911,11 +850,21 @@ class P4Sync(Command):
 
     ## Should move this out, doesn't use SELF.
     def readP4Files(self, files):
+        for f in files:
+            for val in self.clientSpecDirs:
+                if f['path'].startswith(val[0]):
+                    if val[1] > 0:
+                        f['include'] = True
+                    else:
+                        f['include'] = False
+                    break
+
         files = [f for f in files
-                 if f['action'] != 'delete']
+                 if f['action'] != 'delete' and
+                 (f.has_key('include') == False or f['include'] == True)]
 
         if not files:
-            return
+            return []
 
         filedata = p4CmdList('-x - print',
                              stdin='\n'.join(['%s#%s' % (f['path'], f['rev'])
@@ -950,6 +899,7 @@ class P4Sync(Command):
         for f in files:
             assert not f.has_key('data')
             f['data'] = contents[f['path']]
+        return files
 
     def commit(self, details, files, branch, branchPrefixes, parent = ""):
         epoch = details["time"]
@@ -966,11 +916,7 @@ class P4Sync(Command):
                 new_files.append (f)
             else:
                 sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
-        files = new_files
-        self.readP4Files(files)
-
-
-
+        files = self.readP4Files(new_files)
 
         self.gitStream.write("commit %s\n" % branch)
 #        gitStream.write("mark :%s\n" % details["change"])
@@ -1385,6 +1331,26 @@ class P4Sync(Command):
             print self.gitError.read()
 
 
+    def getClientSpec(self):
+        specList = p4CmdList( "client -o" )
+        temp = {}
+        for entry in specList:
+            for k,v in entry.iteritems():
+                if k.startswith("View"):
+                    if v.startswith('"'):
+                        start = 1
+                    else:
+                        start = 0
+                    index = v.find("...")
+                    v = v[start:index]
+                    if v.startswith("-"):
+                        v = v[1:]
+                        temp[v] = -len(v)
+                    else:
+                        temp[v] = len(v)
+        self.clientSpecDirs = temp.items()
+        self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
+
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
@@ -1417,6 +1383,9 @@ class P4Sync(Command):
             if not gitBranchExists(self.refPrefix + "HEAD") and self.importIntoRemotes and gitBranchExists(self.branch):
                 system("git symbolic-ref %sHEAD %s" % (self.refPrefix, self.branch))
 
+        if self.useClientSpec or gitConfig("p4.useclientspec") == "true":
+            self.getClientSpec()
+
         # TODO: should always look at previous commits,
         # merge with previous imports, if possible.
         if args == []:
@@ -1634,13 +1603,23 @@ class P4Clone(P4Sync):
         P4Sync.__init__(self)
         self.description = "Creates a new git repository and imports from Perforce into it"
         self.usage = "usage: %prog [options] //depot/path[@revRange]"
-        self.options.append(
+        self.options += [
             optparse.make_option("--destination", dest="cloneDestination",
                                  action='store', default=None,
-                                 help="where to leave result of the clone"))
+                                 help="where to leave result of the clone"),
+            optparse.make_option("-/", dest="cloneExclude",
+                                 action="append", type="string",
+                                 help="exclude depot path")
+        ]
         self.cloneDestination = None
         self.needsGit = False
 
+    # This is required for the "append" cloneExclude action
+    def ensure_value(self, attr, value):
+        if not hasattr(self, attr) or getattr(self, attr) is None:
+            setattr(self, attr, value)
+        return getattr(self, attr)
+
     def defaultDestination(self, args):
         ## TODO: use common prefix of args?
         depotPath = args[0]
@@ -1664,6 +1643,7 @@ class P4Clone(P4Sync):
             self.cloneDestination = depotPaths[-1]
             depotPaths = depotPaths[:-1]
 
+        self.cloneExclude = ["/"+p for p in self.cloneExclude]
         for p in depotPaths:
             if not p.startswith("//"):
                 return False
diff --git a/copy.c b/copy.c
index c225d1b..afc4fbf 100644 (file)
--- 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 8f70500..a74ed86 100644 (file)
--- 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 */
 };
 
index 03eaa7c..94b150e 100644 (file)
@@ -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 699b21f..ad16164 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -118,8 +118,7 @@ static int parse_funcname_pattern(const char *var, const char *ep, const char *v
                pp->next = funcname_pattern_list;
                funcname_pattern_list = pp;
        }
-       if (pp->pattern)
-               free(pp->pattern);
+       free(pp->pattern);
        pp->pattern = xstrdup(value);
        return 0;
 }
@@ -272,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--) {
@@ -331,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)
@@ -492,10 +491,8 @@ static void free_diff_words_data(struct emit_callback *ecbdata)
                                ecbdata->diff_words->plus.text.size)
                        diff_words_show(ecbdata->diff_words);
 
-               if (ecbdata->diff_words->minus.text.ptr)
-                       free (ecbdata->diff_words->minus.text.ptr);
-               if (ecbdata->diff_words->plus.text.ptr)
-                       free (ecbdata->diff_words->plus.text.ptr);
+               free (ecbdata->diff_words->minus.text.ptr);
+               free (ecbdata->diff_words->plus.text.ptr);
                free(ecbdata->diff_words);
                ecbdata->diff_words = NULL;
        }
@@ -982,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;
@@ -1399,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)
 {
@@ -1413,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");
@@ -1838,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;
@@ -1857,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,
@@ -1866,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;
        }
@@ -1907,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();
@@ -1916,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);
 
@@ -1988,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);
 }
@@ -2017,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);
 
@@ -2029,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 */
@@ -2037,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)
@@ -2050,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 = "";
 
@@ -2083,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 |
@@ -2091,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);
 
@@ -2102,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);
@@ -2212,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"))
@@ -2271,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"))
@@ -2482,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);
        }
 }
 
@@ -2684,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)
@@ -2930,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));
@@ -2940,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)
@@ -3171,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);
 
@@ -3200,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);
@@ -3214,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 8e73f07..9a652e7 100644 (file)
--- 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/dir.c b/dir.c
index 1f507da..edc458e 100644 (file)
--- a/dir.c
+++ b/dir.c
@@ -704,8 +704,7 @@ static struct path_simplify *create_simplify(const char **pathspec)
 
 static void free_simplify(struct path_simplify *simplify)
 {
-       if (simplify)
-               free(simplify);
+       free(simplify);
 }
 
 int read_directory(struct dir_struct *dir, const char *path, const char *base, int baselen, const char **pathspec)
index 3527f16..6739a3f 100644 (file)
@@ -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;
index 74715ed..2c32d0b 100755 (executable)
@@ -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
 }
index 081d755..01e0a46 100644 (file)
@@ -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
index 5d65272..238a239 100755 (executable)
@@ -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
index 86faf24..0adcf9d 100644 (file)
@@ -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
index 0fdd753..08a2462 100644 (file)
@@ -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
 }
index 46da0f4..3ce32b5 100755 (executable)
@@ -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
index fb12b03..c2bedd6 100755 (executable)
@@ -268,6 +268,10 @@ do_next () {
                warn
                warn "  git commit --amend"
                warn
+               warn "Once you are satisfied with your changes, run"
+               warn
+               warn "  git rebase --continue"
+               warn
                exit 0
                ;;
        squash|s)
index 05fb358..9e2faf9 100755 (executable)
@@ -3643,6 +3643,7 @@ sub _auth_providers () {
          SVN::Client::get_ssl_client_cert_file_provider(),
          SVN::Client::get_ssl_client_cert_prompt_provider(
            \&Git::SVN::Prompt::ssl_client_cert, 2),
+         SVN::Client::get_ssl_client_cert_pw_file_provider(),
          SVN::Client::get_ssl_client_cert_pw_prompt_provider(
            \&Git::SVN::Prompt::ssl_client_cert_pw, 2),
          SVN::Client::get_username_provider(),
diff --git a/git.c b/git.c
index 0cb8688..9cca81a 100644 (file)
--- 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 },
index 326e27c..fc95e2c 100755 (executable)
@@ -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);
index 0a58f3f..61e7160 100644 (file)
@@ -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 8143dcd..e57a50e 100644 (file)
--- 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;
index 0beb740..406270f 100644 (file)
@@ -664,8 +664,7 @@ static void release_request(struct transfer_request *request)
                close(request->local_fileno);
        if (request->local_stream)
                fclose(request->local_stream);
-       if (request->url != NULL)
-               free(request->url);
+       free(request->url);
        free(request);
 }
 
@@ -1283,10 +1282,8 @@ static struct remote_lock *lock_remote(const char *path, long timeout)
        strbuf_release(&in_buffer);
 
        if (lock->token == NULL || lock->timeout <= 0) {
-               if (lock->token != NULL)
-                       free(lock->token);
-               if (lock->owner != NULL)
-                       free(lock->owner);
+               free(lock->token);
+               free(lock->owner);
                free(url);
                free(lock);
                lock = NULL;
@@ -1344,8 +1341,7 @@ static int unlock_remote(struct remote_lock *lock)
                        prev->next = prev->next->next;
        }
 
-       if (lock->owner != NULL)
-               free(lock->owner);
+       free(lock->owner);
        free(lock->url);
        free(lock->token);
        free(lock);
@@ -2035,8 +2031,7 @@ static void fetch_symref(const char *path, char **symref, unsigned char *sha1)
        }
        free(url);
 
-       if (*symref != NULL)
-               free(*symref);
+       free(*symref);
        *symref = NULL;
        hashclr(sha1);
 
@@ -2435,8 +2430,7 @@ int main(int argc, char **argv)
        }
 
  cleanup:
-       if (rewritten_url)
-               free(rewritten_url);
+       free(rewritten_url);
        if (info_ref_lock)
                unlock_remote(info_ref_lock);
        free(remote);
index 9025d9a..10cce15 100644 (file)
@@ -472,7 +472,7 @@ v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb,
        if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) {
                free( cmd->cmd );
                free( cmd );
-               if (cb && cb->data)
+               if (cb)
                        free( cb->data );
                return NULL;
        }
@@ -858,8 +858,7 @@ get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd )
                  normal:
                        if (cmdp->cb.done)
                                cmdp->cb.done( ctx, cmdp, resp );
-                       if (cmdp->cb.data)
-                               free( cmdp->cb.data );
+                       free( cmdp->cb.data );
                        free( cmdp->cmd );
                        free( cmdp );
                        if (!tcmd || tcmd == cmdp)
index 6ef53f2..7f03bd9 100644 (file)
@@ -11,8 +11,7 @@ void interp_set_entry(struct interp *table, int slot, const char *value)
        char *oldval = table[slot].value;
        char *newval = NULL;
 
-       if (oldval)
-               free(oldval);
+       free(oldval);
 
        if (value)
                newval = xstrdup(value);
index e9ba6df..608f697 100644 (file)
@@ -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)
index b33f7cd..0cc9344 100644 (file)
@@ -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 (file)
index 0000000..f37630a
--- /dev/null
@@ -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
index 997f583..703f521 100644 (file)
--- a/pretty.c
+++ b/pretty.c
@@ -30,8 +30,7 @@ enum cmit_fmt get_commit_format(const char *arg)
        if (*arg == '=')
                arg++;
        if (!prefixcmp(arg, "format:")) {
-               if (user_format)
-                       free(user_format);
+               free(user_format);
                user_format = xstrdup(arg + 7);
                return CMIT_FMT_USERFORMAT;
        }
@@ -110,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;
@@ -621,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;
 
@@ -686,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 (;;) {
index e45f4b3..657f0c5 100644 (file)
@@ -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;
index 3267495..a971433 100644 (file)
@@ -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 bc65429..1b0050e 100644 (file)
--- 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,