Merge branch 'ss/svnimport'
authorJunio C Hamano <gitster@pobox.com>
Sat, 29 Sep 2007 23:44:25 +0000 (16:44 -0700)
committerJunio C Hamano <gitster@pobox.com>
Sat, 29 Sep 2007 23:44:25 +0000 (16:44 -0700)
* ss/svnimport:
Fix pool handling in git-svnimport to avoid memory leaks.

88 files changed:
.gitignore
Documentation/RelNotes-1.5.3.3.txt [new file with mode: 0644]
Documentation/RelNotes-1.5.4.txt [new file with mode: 0644]
Documentation/cmd-list.perl
Documentation/config.txt
Documentation/core-tutorial.txt
Documentation/git-apply.txt
Documentation/git-bundle.txt
Documentation/git-convert-objects.txt [deleted file]
Documentation/git-merge.txt
Documentation/git-pack-objects.txt
Documentation/git-rebase.txt
Documentation/git-remote.txt
Documentation/git-send-email.txt
Documentation/git-submodule.txt
Documentation/git.txt
Documentation/gitattributes.txt
Documentation/gitignore.txt
Documentation/hooks.txt
Documentation/user-manual.txt
GIT-VERSION-GEN
Makefile
RelNotes
archive-tar.c
archive-zip.c
archive.h
builtin-add.c
builtin-apply.c
builtin-archive.c
builtin-fetch--tool.c
builtin-mv.c
builtin-pack-objects.c
builtin-reset.c [new file with mode: 0644]
builtin-rev-list.c
builtin-rm.c
builtin-update-index.c
builtin-update-ref.c
builtin-verify-tag.c
builtin.h
cache.h
commit.c
commit.h
compat/memmem.c [new file with mode: 0644]
contrib/completion/git-completion.bash
contrib/convert-objects/convert-objects.c [new file with mode: 0644]
contrib/convert-objects/git-convert-objects.txt [new file with mode: 0644]
contrib/emacs/git.el
contrib/examples/git-reset.sh [new file with mode: 0755]
contrib/fast-import/git-import.perl [new file with mode: 0755]
contrib/fast-import/git-import.sh [new file with mode: 0755]
contrib/fast-import/git-p4
contrib/hooks/post-receive-email
contrib/hooks/setgitperms.perl [new file with mode: 0644]
convert-objects.c [deleted file]
convert.c
diff-delta.c
diff.c
diffcore-rename.c
diffcore.h
git-am.sh
git-commit.sh
git-compat-util.h
git-merge.sh
git-mergetool.sh
git-quiltimport.sh
git-rebase--interactive.sh
git-rebase.sh
git-remote.perl
git-reset.sh [deleted file]
git-send-email.perl
git-submodule.sh
git-svn.perl
git.c
gitweb/gitweb.perl
merge-recursive.c
read-cache.c
refs.c
refs.h
revision.c
send-pack.c
t/t3404-rebase-interactive.sh
t/t5000-tar-tree.sh
t/t5402-post-merge-hook.sh [new file with mode: 0755]
t/t5505-remote.sh [new file with mode: 0755]
t/t7102-reset.sh [new file with mode: 0755]
t/t9001-send-email.sh
t/t9101-git-svn-props.sh
t/t9104-git-svn-follow-parent.sh
index 63c918c667fa005ff12ad89437f2fdc80926e21c..e0b91befb9012b8c2e9e04a4c2c5d7845b7c5e4a 100644 (file)
@@ -25,7 +25,6 @@ git-clone
 git-commit
 git-commit-tree
 git-config
-git-convert-objects
 git-count-objects
 git-cvsexportcommit
 git-cvsimport
diff --git a/Documentation/RelNotes-1.5.3.3.txt b/Documentation/RelNotes-1.5.3.3.txt
new file mode 100644 (file)
index 0000000..e91bd84
--- /dev/null
@@ -0,0 +1,37 @@
+GIT v1.5.3.3 Release Notes
+==========================
+
+Fixes since v1.5.3.2
+--------------------
+
+ * git-quiltimport did not like it when a patch described in the
+   series file does not exist.
+
+ * p4 importer missed executable bit in some cases.
+
+ * The default shell on some FreeBSD did not execute the
+   argument parsing code correctly and made git unusable.
+
+ * git-svn incorrectly spawned pager even when the user user
+   explicitly asked not to.
+
+ * sample post-receive hook overquoted the envelope sender
+   value.
+
+ * git-am got confused when the patch contained a change that is
+   only about type and not contents.
+
+ * git-mergetool did not show our and their version of the
+   conflicted file when started from a subdirectory of the
+   project.
+
+ * git-mergetool did not pass correct options when invoking diff3.
+
+ * git-log sometimes invoked underlying "diff" machinery
+   unnecessarily.
+
+--
+exec >/var/tmp/1
+O=v1.5.3.2-29-gb7bb760
+echo O=`git describe refs/heads/maint`
+git shortlog --no-merges $O..refs/heads/maint
diff --git a/Documentation/RelNotes-1.5.4.txt b/Documentation/RelNotes-1.5.4.txt
new file mode 100644 (file)
index 0000000..ceee857
--- /dev/null
@@ -0,0 +1,35 @@
+GIT v1.5.4 Release Notes
+========================
+
+Updates since v1.5.3
+--------------------
+
+ * git-reset is now built-in.
+
+ * git-send-email can optionally talk over ssmtp and use SMTP-AUTH.
+
+ * git-rebase learned --whitespace option.
+
+ * git-remote knows --mirror mode.
+
+ * git-merge can call the "post-merge" hook.
+
+ * git-pack-objects can optionally run deltification with multiple threads.
+
+ * git-archive can optionally substitute keywords in files marked with
+   export-subst attribute.
+
+ * Various Perforce importer updates.
+
+Fixes since v1.5.3
+------------------
+
+All of the fixes in v1.5.3 maintenance series are included in
+this release, unless otherwise noted.
+
+--
+exec >/var/tmp/1
+O=v1.5.3.2-99-ge4b2890
+echo O=`git describe refs/heads/master`
+git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint
+
index 4ee76eaf9925f98dcd77dc74f1f7a9eb9030fc74..1061fd8bcdf44964af2b6c55d4c9cb3c0c21f968 100755 (executable)
@@ -94,7 +94,6 @@ sub format_one {
 git-commit                              mainporcelain
 git-commit-tree                         plumbingmanipulators
 git-config                              ancillarymanipulators
-git-convert-objects                     ancillarymanipulators
 git-count-objects                       ancillaryinterrogators
 git-cvsexportcommit                     foreignscminterface
 git-cvsimport                           foreignscminterface
index 866e0534b8843af3c1789becd06b244162632d01..015910f27a450cdaec80f3bfc2679243126736c0 100644 (file)
@@ -630,9 +630,17 @@ pack.deltaCacheSize::
        A value of 0 means no limit. Defaults to 0.
 
 pack.deltaCacheLimit::
-       The maxium size of a delta, that is cached in
+       The maximum size of a delta, that is cached in
        gitlink:git-pack-objects[1]. Defaults to 1000.
 
+pack.threads::
+       Specifies the number of threads to spawn when searching for best
+       delta matches.  This requires that gitlink:git-pack-objects[1]
+       be compiled with pthreads otherwise this option is ignored with a
+       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.
+
 pull.octopus::
        The default merge strategy to use when pulling multiple branches
        at once.
index 4b4fd9a50639ad6e1517e426c027c5855c1b79f7..6b2590d0723ad94a45c9cae174935839df3331d8 100644 (file)
@@ -1459,7 +1459,8 @@ Although git is a truly distributed system, it is often
 convenient to organize your project with an informal hierarchy
 of developers. Linux kernel development is run this way. There
 is a nice illustration (page 17, "Merges to Mainline") in
-link:http://tinyurl.com/a2jdg[Randy Dunlap's presentation].
+link:http://www.xenotime.net/linux/mentor/linux-mentoring-2006.pdf
+[Randy Dunlap's presentation].
 
 It should be stressed that this hierarchy is purely *informal*.
 There is nothing fundamental in git that enforces the "chain of
index 4c7e3a2f7f5d542cd95059430f53143bba59fc7f..c1c54bfe0b7d2c1b133e245a3a963caa0b7afb8c 100644 (file)
@@ -10,7 +10,7 @@ SYNOPSIS
 --------
 [verse]
 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index]
-         [--apply] [--no-add] [--index-info] [-R | --reverse]
+         [--apply] [--no-add] [--build-fake-ancestor <file>] [-R | --reverse]
          [--allow-binary-replacement | --binary] [--reject] [-z]
          [-pNUM] [-CNUM] [--inaccurate-eof] [--cached]
          [--whitespace=<nowarn|warn|error|error-all|strip>]
@@ -63,12 +63,15 @@ OPTIONS
        cached data, apply the patch, and store the result in the index,
        without using the working tree. This implies '--index'.
 
---index-info::
+--build-fake-ancestor <file>::
        Newer git-diff output has embedded 'index information'
        for each blob to help identify the original version that
        the patch applies to.  When this flag is given, and if
-       the original version of the blob is available locally,
-       outputs information about them to the standard output.
+       the original versions of the blobs is available locally,
+       builds a temporary index containing those blobs.
++
+When a pure mode change is encountered (which has no index information),
+the information is read from the current index instead.
 
 -R, --reverse::
        Apply the patch in reverse.
index 5051e2bada6d2409f40a04bbd25a5201df7d836e..0cc6511bdf35f29ce946726485f71eeddddce56b 100644 (file)
@@ -103,14 +103,20 @@ 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)
 
 in R2 on B:
+
+------------
 $ git-bundle verify mybundle
 $ git-fetch mybundle  refspec
+------------
 
 where refspec is refInBundle:localRef
 
@@ -124,9 +130,11 @@ Also, with something like this in your config:
 You can first sneakernet the bundle file to ~/tmp/file.bdl and
 then these commands:
 
+------------
 $ git ls-remote bundle
 $ git fetch bundle
 $ git pull bundle
+------------
 
 would treat it as if it is talking with a remote side over the
 network.
diff --git a/Documentation/git-convert-objects.txt b/Documentation/git-convert-objects.txt
deleted file mode 100644 (file)
index 9718abf..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-git-convert-objects(1)
-======================
-
-NAME
-----
-git-convert-objects - Converts old-style git repository
-
-
-SYNOPSIS
---------
-'git-convert-objects'
-
-DESCRIPTION
------------
-Converts old-style git repository to the latest format
-
-
-Author
-------
-Written by Linus Torvalds <torvalds@osdl.org>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the gitlink:git[7] suite
index 144bc16ff26bbf1d311482e6be15fc034994ce49..eae49c4876caf6b2e6a8bd9770b3981fb8133edd 100644 (file)
@@ -56,7 +56,7 @@ merge.verbosity::
        message if conflicts were detected. Level 1 outputs only
        conflicts, 2 outputs conflicts and file changes.  Level 5 and
        above outputs debugging information.  The default is level 2.
-       Can be overriden by 'GIT_MERGE_VERBOSITY' environment variable.
+       Can be overridden by 'GIT_MERGE_VERBOSITY' environment variable.
 
 
 HOW MERGE WORKS
index d18259d93fe1daf1785f373b475add8ff9d2f213..5237ab0c046cb3b8468166684b04c9ef8d50e588 100644 (file)
@@ -169,6 +169,14 @@ base-name::
        length, this option typically shrinks the resulting
        packfile by 3-5 per-cent.
 
+--threads=<n>::
+       Specifies the number of threads to spawn when searching for best
+       delta matches.  This requires that pack-objects be compiled with
+       pthreads otherwise this option is ignored with a 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.
+
 --index-version=<version>[,<offset>]::
        This is intended to be used by the test suite only. It allows
        to force the version for the generated pack index, and to force
index 61b1810dbaa56bdc43364ea10d9a5ea0d532f1be..0858fa8a6326a0c457bc9b4bb63ae35d3001ef65 100644 (file)
@@ -8,8 +8,9 @@ git-rebase - Forward-port local commits to the updated upstream head
 SYNOPSIS
 --------
 [verse]
-'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge] [-C<n>]
-       [-p | --preserve-merges] [--onto <newbase>] <upstream> [<branch>]
+'git-rebase' [-i | --interactive] [-v | --verbose] [-m | --merge]
+       [-C<n>] [ --whitespace=<option>] [-p | --preserve-merges]
+       [--onto <newbase>] <upstream> [<branch>]
 'git-rebase' --continue | --skip | --abort
 
 DESCRIPTION
@@ -209,6 +210,10 @@ OPTIONS
        context exist they all must match.  By default no context is
        ever ignored.
 
+--whitespace=<nowarn|warn|error|error-all|strip>::
+       This flag is passed to the `git-apply` program
+       (see gitlink:git-apply[1]) that applies the patch.
+
 -i, \--interactive::
        Make a list of the commits which are about to be rebased.  Let the
        user edit that list before rebasing.  This mode can also be used to
index 61a6022ce8a0fc7aac8b1e9bd08587817ef0d69c..027ba11bdb67ba540b63ec12e5a6b920cd2e5f7e 100644 (file)
@@ -10,7 +10,8 @@ SYNOPSIS
 --------
 [verse]
 'git-remote'
-'git-remote' add [-t <branch>] [-m <branch>] [-f] <name> <url>
+'git-remote' add [-t <branch>] [-m <branch>] [-f] [--mirror] <name> <url>
+'git-remote' rm <name>
 'git-remote' show <name>
 'git-remote' prune <name>
 'git-remote' update [group]
@@ -45,6 +46,15 @@ multiple branches without grabbing all branches.
 With `-m <master>` option, `$GIT_DIR/remotes/<name>/HEAD` is set
 up to point at remote's `<master>` branch instead of whatever
 branch the `HEAD` at the remote repository actually points at.
++
+In mirror mode, enabled with `--mirror`, the refs will not be stored
+in the 'refs/remotes/' namespace, but in 'refs/heads/'.  This option
+only makes sense in bare repositories.
+
+'rm'::
+
+Remove the remote named <name>. All remote tracking branches and
+configuration settings for the remote are removed.
 
 'show'::
 
index 16bfd7be2271d21d8d380e9f4e20307569f3a538..1ec61affab506705499a80ec944b290896b993a0 100644 (file)
@@ -75,6 +75,12 @@ The --cc option must be repeated for each user you want on the cc list.
        Make git-send-email less verbose.  One line per email should be
        all that is output.
 
+--identity::
+       A configuration identity. When given, causes values in the
+       'sendemail.<identity>' subsection to take precedence over
+       values in the 'sendemail' section. The default identity is
+       the value of 'sendemail.identity'.
+
 --smtp-server::
        If set, specifies the outgoing SMTP server to use (e.g.
        `smtp.example.com` or a raw IP address).  Alternatively it can
@@ -85,6 +91,17 @@ The --cc option must be repeated for each user you want on the cc list.
        `/usr/lib/sendmail` if such program is available, or
        `localhost` otherwise.
 
+--smtp-user, --smtp-pass::
+       Username and password for SMTP-AUTH. Defaults are the values of
+       the configuration values 'sendemail.smtpuser' and
+       'sendemail.smtppass', but see also 'sendemail.identity'.
+       If not set, authentication is not attempted.
+
+--smtp-ssl::
+       If set, connects to the SMTP server using SSL.
+       Default is the value of the 'sendemail.smtpssl' configuration value;
+       if that is unspecified, does not use SSL.
+
 --subject::
        Specify the initial subject of the email thread.
        Only necessary if --compose is also set.  If --compose
@@ -122,6 +139,13 @@ The --to option must be repeated for each user you want on the to list.
 
 CONFIGURATION
 -------------
+sendemail.identity::
+       The default configuration identity. When specified,
+       'sendemail.<identity>.<item>' will have higher precedence than
+       'sendemail.<item>'. This is useful to declare multiple SMTP
+       identities and to hoist sensitive authentication information
+       out of the repository and into the global configuation file.
+
 sendemail.aliasesfile::
        To avoid typing long email addresses, point this to one or more
        email aliases files.  You must also supply 'sendemail.aliasfiletype'.
@@ -141,7 +165,16 @@ sendemail.chainreplyto::
        parameter.
 
 sendemail.smtpserver::
-       Default smtp server to use.
+       Default SMTP server to use.
+
+sendemail.smtpuser::
+       Default SMTP-AUTH username.
+
+sendemail.smtppass::
+       Default SMTP-AUTH password.
+
+sendemail.smtpssl::
+       Boolean value specifying the default to the '--smtp-ssl' parameter.
 
 Author
 ------
index 2c48936fcd72c5276ae2fed217bd9b9564342f03..335e973a6a1d5350c1558eb68985ffe812c09212 100644 (file)
@@ -21,6 +21,9 @@ add::
        repository is cloned at the specified path, added to the
        changeset and registered in .gitmodules.   If no path is
        specified, the path is deduced from the repository specification.
+       If the repository url begins with ./ or ../, it is stored as
+       given but resolved as a relative path from the main project's
+       url when cloning.
 
 status::
        Show the status of the submodules. This will print the SHA-1 of the
index a7cd91acc1c6551918d54158a1c76321157e97fa..cb59639f777889cb909933886d31ebcce1bbd199 100644 (file)
@@ -46,6 +46,7 @@ Documentation for older releases are available here:
 * link:v1.5.3/git.html[documentation for release 1.5.3]
 
 * release notes for
+  link:RelNotes-1.5.3.2.txt[1.5.3.2],
   link:RelNotes-1.5.3.1.txt[1.5.3.1].
 
 * release notes for
index 46f9d591aa7a3af2235d29308d101b2bfa0bff56..20cf8ff81673265629028b49c34e0393063fd6b1 100644 (file)
@@ -145,17 +145,6 @@ sign `$` upon checkout.  Any byte sequence that begins with
 with `$Id$` upon check-in.
 
 
-Interaction between checkin/checkout attributes
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-In the check-in codepath, the worktree file is first converted
-with `ident` (if specified), and then with `crlf` (again, if
-specified and applicable).
-
-In the check-out codepath, the blob content is first converted
-with `crlf`, and then `ident`.
-
-
 `filter`
 ^^^^^^^^
 
@@ -175,11 +164,10 @@ but makes the filter a no-op passthru.
 The content filtering is done to massage the content into a
 shape that is more convenient for the platform, filesystem, and
 the user to use.  The keyword here is "more convenient" and not
-"turning something unusable into usable".  In other words, it is
-"hanging yourself because we gave you a long rope" if your
-project uses filtering mechanism in such a way that it makes
-your project unusable unless the checkout is done with a
-specific filter in effect.
+"turning something unusable into usable".  In other words, the
+intent is that if someone unsets the filter driver definition,
+or does not have the appropriate filter program, the project
+should still be usable.
 
 
 Interaction between checkin/checkout attributes
@@ -421,6 +409,23 @@ frotz      unspecified
 ----------------------------------------------------------------
 
 
+Creating an archive
+~~~~~~~~~~~~~~~~~~~
+
+`export-subst`
+^^^^^^^^^^^^^^
+
+If the attribute `export-subst` is set for a file then git will expand
+several placeholders when adding this file to an archive.  The
+expansion depends on the availability of a commit ID, i.e. if
+gitlink:git-archive[1] has been given a tree instead of a commit or a
+tag then no replacement will be done.  The placeholders are the same
+as those for the option `--pretty=format:` of gitlink:git-log[1],
+except that they need to be wrapped like this: `$Format:PLACEHOLDERS$`
+in the file.  E.g. the string `$Format:%H$` will be replaced by the
+commit hash.
+
+
 GIT
 ---
 Part of the gitlink:git[7] suite
index 9c83095693447a46f36299f417d71262890fb098..e8b8581f5280eefc470b216a80210f5f552a780a 100644 (file)
@@ -26,7 +26,7 @@ precedence, the last matching pattern decides the outcome):
 
  * Patterns read from a `.gitignore` file in the same directory
    as the path, or in any parent directory, with patterns in the
-   higher level files (up to the root) being overriden by those in
+   higher level files (up to the root) being overridden by those in
    lower level files down to the directory containing the file.
    These patterns match relative to the location of the
    `.gitignore` file.  A project normally includes such
index c39edc57c4452091e2f313cb8d5cfa9d51a4b27b..58b954759610ca272b72a05db0cd14e2868ff301 100644 (file)
@@ -87,6 +87,19 @@ parameter, and is invoked after a commit is made.
 This hook is meant primarily for notification, and cannot affect
 the outcome of `git-commit`.
 
+post-merge
+-----------
+
+This hook is invoked by `git-merge`, which happens when a `git pull`
+is done on a local repository.  The hook takes a single parameter, a status
+flag specifying whether or not the merge being done was a squash merge.
+This hook cannot affect the outcome of `git-merge`.
+
+This hook can be used in conjunction with a corresponding pre-commit hook to
+save and restore any form of metadata associated with the working tree
+(eg: permissions/ownership, ACLS, etc).  See contrib/hooks/setgitperms.perl
+for an example of how to do this.
+
 [[pre-receive]]
 pre-receive
 -----------
index a085ca1d3919205d20923d73301f3d79ea1605af..c7fdf25e27c94e9b152592a28bf7acc64dfa0e07 100644 (file)
@@ -2856,8 +2856,7 @@ between two related tree objects, since it can ignore any entries with
 identical object names.
 
 (Note: in the presence of submodules, trees may also have commits as
-entries.   See gitlink:git-submodule[1] and gitlink:gitmodules.txt[1]
-for partial documentation.)
+entries.  See <<submodules>> for documentation.)
 
 Note that the files all have mode 644 or 755: git actually only pays
 attention to the executable bit.
@@ -3163,12 +3162,45 @@ information as long as you have the name of the tree that it described.
 Submodules
 ==========
 
-This tutorial explains how to create and publish a repository with submodules
-using the gitlink:git-submodule[1] command.
-
-Submodules maintain their own identity; the submodule support just stores the
-submodule repository location and commit ID, so other developers who clone the
-superproject can easily clone all the submodules at the same revision.
+Large projects are often composed of smaller, self-contained modules.  For
+example, an embedded Linux distribution's source tree would include every
+piece of software in the distribution with some local modifications; a movie
+player might need to build against a specific, known-working version of a
+decompression library; several independent programs might all share the same
+build scripts.
+
+With centralized revision control systems this is often accomplished by
+including every module in one single repository.  Developers can check out
+all modules or only the modules they need to work with.  They can even modify
+files across several modules in a single commit while moving things around
+or updating APIs and translations.
+
+Git does not allow partial checkouts, so duplicating this approach in Git
+would force developers to keep a local copy of modules they are not
+interested in touching.  Commits in an enormous checkout would be slower
+than you'd expect as Git would have to scan every directory for changes.
+If modules have a lot of local history, clones would take forever.
+
+On the plus side, distributed revision control systems can much better
+integrate with external sources.  In a centralized model, a single arbitrary
+snapshot of the external project is exported from its own revision control
+and then imported into the local revision control on a vendor branch.  All
+the history is hidden.  With distributed revision control you can clone the
+entire external history and much more easily follow development and re-merge
+local changes.
+
+Git's submodule support allows a repository to contain, as a subdirectory, a
+checkout of an external project.  Submodules maintain their own identity;
+the submodule support just stores the submodule repository location and
+commit ID, so other developers who clone the containing project
+("superproject") can easily clone all the submodules at the same revision.
+Partial checkouts of the superproject are possible: you can tell Git to
+clone none, some or all of the submodules.
+
+The gitlink:git-submodule[1] command is available since Git 1.5.3.  Users
+with Git 1.5.2 can look up the submodule commits in the repository and
+manually check them out; earlier versions won't recognize the submodules at
+all.
 
 To see how submodule support works, create (for example) four example
 repositories that can be used later as a submodule:
@@ -3213,8 +3245,8 @@ The `git submodule add` command does a couple of things:
 
 - It clones the submodule under the current directory and by default checks out
   the master branch.
-- It adds the submodule's clone path to the `.gitmodules` file and adds this
-  file to the index, ready to be committed.
+- It adds the submodule's clone path to the gitlink:gitmodules[5] file and
+  adds this file to the index, ready to be committed.
 - It adds the submodule's current commit ID to the index, ready to be
   committed.
 
@@ -4277,5 +4309,3 @@ Write a chapter on using plumbing and writing scripts.
 Alternates, clone -reference, etc.
 
 git unpack-objects -r for recovery
-
-submodules
index 7fbde233b86812b2bda51878cf4d09ad18c2062b..3c0032cec592a765692234f1cba47dfdcc3a9200 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.5.3.2.GIT
+DEF_VER=v1.5.3.GIT
 
 LF='
 '
index dace2112f903f6e5be8220deb3b1502496876bb1..8db4dbe1d2f75ce9d4562f9518b0298e806c86db 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -28,6 +28,8 @@ all::
 #
 # Define NO_STRCASESTR if you don't have strcasestr.
 #
+# Define NO_MEMMEM if you don't have memmem.
+#
 # Define NO_STRLCPY if you don't have strlcpy.
 #
 # Define NO_STRTOUMAX if you don't have strtoumax in the C library.
@@ -122,6 +124,9 @@ all::
 # If not set it defaults to the bare 'wish'. If it is set to the empty
 # string then NO_TCLTK will be forced (this is used by configure script).
 #
+# Define THREADED_DELTA_SEARCH if you have pthreads and wish to exploit
+# parallel delta searching when packing objects.
+#
 
 GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
        @$(SHELL_PATH) ./GIT-VERSION-GEN
@@ -206,7 +211,7 @@ SCRIPT_SH = \
        git-ls-remote.sh \
        git-merge-one-file.sh git-mergetool.sh git-parse-remote.sh \
        git-pull.sh git-rebase.sh git-rebase--interactive.sh \
-       git-repack.sh git-request-pull.sh git-reset.sh \
+       git-repack.sh git-request-pull.sh \
        git-sh-setup.sh \
        git-am.sh \
        git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \
@@ -228,7 +233,7 @@ SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH)) \
 
 # ... and all the rest that could be moved out of bindir to gitexecdir
 PROGRAMS = \
-       git-convert-objects$X git-fetch-pack$X \
+       git-fetch-pack$X \
        git-hash-object$X git-index-pack$X git-local-fetch$X \
        git-fast-import$X \
        git-daemon$X \
@@ -353,6 +358,7 @@ BUILTIN_OBJS = \
        builtin-reflog.o \
        builtin-config.o \
        builtin-rerere.o \
+       builtin-reset.o \
        builtin-rev-list.o \
        builtin-rev-parse.o \
        builtin-revert.o \
@@ -396,12 +402,14 @@ ifeq ($(uname_S),Darwin)
        NEEDS_LIBICONV = YesPlease
        OLD_ICONV = UnfortunatelyYes
        NO_STRLCPY = YesPlease
+       NO_MEMMEM = YesPlease
 endif
 ifeq ($(uname_S),SunOS)
        NEEDS_SOCKET = YesPlease
        NEEDS_NSL = YesPlease
        SHELL_PATH = /bin/bash
        NO_STRCASESTR = YesPlease
+       NO_MEMMEM = YesPlease
        NO_HSTRERROR = YesPlease
        ifeq ($(uname_R),5.8)
                NEEDS_LIBICONV = YesPlease
@@ -424,6 +432,7 @@ ifeq ($(uname_O),Cygwin)
        NO_D_TYPE_IN_DIRENT = YesPlease
        NO_D_INO_IN_DIRENT = YesPlease
        NO_STRCASESTR = YesPlease
+       NO_MEMMEM = YesPlease
        NO_SYMLINK_HEAD = YesPlease
        NEEDS_LIBICONV = YesPlease
        NO_FAST_WORKING_DIRECTORY = UnfortunatelyYes
@@ -437,11 +446,13 @@ ifeq ($(uname_O),Cygwin)
 endif
 ifeq ($(uname_S),FreeBSD)
        NEEDS_LIBICONV = YesPlease
+       NO_MEMMEM = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
 endif
 ifeq ($(uname_S),OpenBSD)
        NO_STRCASESTR = YesPlease
+       NO_MEMMEM = YesPlease
        NEEDS_LIBICONV = YesPlease
        BASIC_CFLAGS += -I/usr/local/include
        BASIC_LDFLAGS += -L/usr/local/lib
@@ -456,6 +467,7 @@ ifeq ($(uname_S),NetBSD)
 endif
 ifeq ($(uname_S),AIX)
        NO_STRCASESTR=YesPlease
+       NO_MEMMEM = YesPlease
        NO_STRLCPY = YesPlease
        NEEDS_LIBICONV=YesPlease
 endif
@@ -467,6 +479,7 @@ ifeq ($(uname_S),IRIX64)
        NO_IPV6=YesPlease
        NO_SETENV=YesPlease
        NO_STRCASESTR=YesPlease
+       NO_MEMMEM = YesPlease
        NO_STRLCPY = YesPlease
        NO_SOCKADDR_STORAGE=YesPlease
        SHELL_PATH=/usr/gnu/bin/bash
@@ -661,6 +674,15 @@ ifdef NO_HSTRERROR
        COMPAT_CFLAGS += -DNO_HSTRERROR
        COMPAT_OBJS += compat/hstrerror.o
 endif
+ifdef NO_MEMMEM
+       COMPAT_CFLAGS += -DNO_MEMMEM
+       COMPAT_OBJS += compat/memmem.o
+endif
+
+ifdef THREADED_DELTA_SEARCH
+       BASIC_CFLAGS += -DTHREADED_DELTA_SEARCH
+       EXTLIBS += -lpthread
+endif
 
 ifeq ($(TCLTK_PATH),)
 NO_TCLTK=NoThanks
@@ -783,7 +805,7 @@ perl/perl.mak: GIT-CFLAGS
 
 $(patsubst %.perl,%,$(SCRIPT_PERL)): % : %.perl
        $(QUIET_GEN)$(RM) $@ $@+ && \
-       INSTLIBDIR=`$(MAKE) -C perl -s --no-print-directory instlibdir` && \
+       INSTLIBDIR=`MAKEFLAGS= $(MAKE) -C perl -s --no-print-directory instlibdir` && \
        sed -e '1{' \
            -e '        s|#!.*perl|#!$(PERL_PATH_SQ)|' \
            -e '        h' \
index 599a85530313073e1eab0ba90a325b72bc12b27b..46308cee0ba1ca2cf7d7d9c3de6abafe20c1493f 120000 (symlink)
--- a/RelNotes
+++ b/RelNotes
@@ -1 +1 @@
-Documentation/RelNotes-1.5.3.2.txt
\ No newline at end of file
+Documentation/RelNotes-1.5.4.txt
\ No newline at end of file
index 66fe3e375b545613faba4e051dc41c1acb5d8cee..c0d95dab0d965ff0ae961d72d7eae4c021c5c05a 100644 (file)
@@ -17,6 +17,7 @@ static unsigned long offset;
 static time_t archive_time;
 static int tar_umask = 002;
 static int verbose;
+static const struct commit *commit;
 
 /* writes out the whole block, but only if it is full */
 static void write_if_needed(void)
@@ -285,7 +286,8 @@ static int write_tar_entry(const unsigned char *sha1,
                buffer = NULL;
                size = 0;
        } else {
-               buffer = convert_sha1_file(path.buf, sha1, mode, &type, &size);
+               buffer = sha1_file_to_archive(path.buf, sha1, mode, &type,
+                                             &size, commit);
                if (!buffer)
                        die("cannot read %s", sha1_to_hex(sha1));
        }
@@ -304,6 +306,7 @@ int write_tar_archive(struct archiver_args *args)
 
        archive_time = args->time;
        verbose = args->verbose;
+       commit = args->commit;
 
        if (args->commit_sha1)
                write_global_extended_header(args->commit_sha1);
index 444e1623db66fe4f5d8983e1e9e2cf4083b005b4..74e30f6205f41112dc2bafe9371a790aca55f70c 100644 (file)
@@ -12,6 +12,7 @@
 static int verbose;
 static int zip_date;
 static int zip_time;
+static const struct commit *commit;
 
 static unsigned char *zip_dir;
 static unsigned int zip_dir_size;
@@ -191,11 +192,13 @@ static int write_zip_entry(const unsigned char *sha1,
                compressed_size = 0;
        } else if (S_ISREG(mode) || S_ISLNK(mode)) {
                method = 0;
-               attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : 0;
+               attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) :
+                       (mode & 0111) ? ((mode) << 16) : 0;
                if (S_ISREG(mode) && zlib_compression_level != 0)
                        method = 8;
                result = 0;
-               buffer = convert_sha1_file(path, sha1, mode, &type, &size);
+               buffer = sha1_file_to_archive(path, sha1, mode, &type, &size,
+                                             commit);
                if (!buffer)
                        die("cannot read %s", sha1_to_hex(sha1));
                crc = crc32(crc, buffer, size);
@@ -229,7 +232,8 @@ static int write_zip_entry(const unsigned char *sha1,
        }
 
        copy_le32(dirent.magic, 0x02014b50);
-       copy_le16(dirent.creator_version, S_ISLNK(mode) ? 0x0317 : 0);
+       copy_le16(dirent.creator_version,
+               S_ISLNK(mode) || (S_ISREG(mode) && (mode & 0111)) ? 0x0317 : 0);
        copy_le16(dirent.version, 10);
        copy_le16(dirent.flags, 0);
        copy_le16(dirent.compression_method, method);
@@ -316,6 +320,7 @@ int write_zip_archive(struct archiver_args *args)
        zip_dir = xmalloc(ZIP_DIRECTORY_MIN_SIZE);
        zip_dir_size = ZIP_DIRECTORY_MIN_SIZE;
        verbose = args->verbose;
+       commit = args->commit;
 
        if (args->base && plen > 0 && args->base[plen - 1] == '/') {
                char *base = xstrdup(args->base);
index 6838dc788f7620b0807a7044b611efc623bdcf0c..5791e657e9a0c22081f4f42b9d8ca5b3c536baf2 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -8,6 +8,7 @@ struct archiver_args {
        const char *base;
        struct tree *tree;
        const unsigned char *commit_sha1;
+       const struct commit *commit;
        time_t time;
        const char **pathspec;
        unsigned int verbose : 1;
@@ -42,4 +43,6 @@ extern int write_tar_archive(struct archiver_args *);
 extern int write_zip_archive(struct archiver_args *);
 extern void *parse_extra_zip_args(int argc, const char **argv);
 
+extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit);
+
 #endif /* ARCHIVE_H */
index 3d8b8b4f89514e0a8f7af1c2c7dc2f8ae372129e..0d7d0ce4209114245ca07842d7d5a4546e5b6cfd 100644 (file)
@@ -103,7 +103,6 @@ static void update_callback(struct diff_queue_struct *q,
                        break;
                case DIFF_STATUS_DELETED:
                        remove_file_from_cache(path);
-                       cache_tree_invalidate_path(active_cache_tree, path);
                        if (verbose)
                                printf("remove '%s'\n", path);
                        break;
index bd969778e063bab70eab14d6261d07edd926a115..6777231c665044a486c16fd7fb11b53ed83a0474 100644 (file)
@@ -41,7 +41,7 @@ static int apply_in_reverse;
 static int apply_with_reject;
 static int apply_verbosely;
 static int no_add;
-static int show_index_info;
+static const char *fake_ancestor;
 static int line_termination = '\n';
 static unsigned long p_context = ULONG_MAX;
 static const char apply_usage[] =
@@ -2248,9 +2248,12 @@ static int get_current_sha1(const char *path, unsigned char *sha1)
        return 0;
 }
 
-static void show_index_list(struct patch *list)
+/* Build an index that contains the just the files needed for a 3way merge */
+static void build_fake_ancestor(struct patch *list, const char *filename)
 {
        struct patch *patch;
+       struct index_state result = { 0 };
+       int fd;
 
        /* Once we start supporting the reverse patch, it may be
         * worth showing the new sha1 prefix, but until then...
@@ -2258,11 +2261,12 @@ static void show_index_list(struct patch *list)
        for (patch = list; patch; patch = patch->next) {
                const unsigned char *sha1_ptr;
                unsigned char sha1[20];
+               struct cache_entry *ce;
                const char *name;
 
                name = patch->old_name ? patch->old_name : patch->new_name;
                if (0 < patch->is_new)
-                       sha1_ptr = null_sha1;
+                       continue;
                else if (get_sha1(patch->old_sha1_prefix, sha1))
                        /* git diff has no index line for mode/type changes */
                        if (!patch->lines_added && !patch->lines_deleted) {
@@ -2277,13 +2281,16 @@ static void show_index_list(struct patch *list)
                else
                        sha1_ptr = sha1;
 
-               printf("%06o %s ",patch->old_mode, sha1_to_hex(sha1_ptr));
-               if (line_termination && quote_c_style(name, NULL, NULL, 0))
-                       quote_c_style(name, NULL, stdout, 0);
-               else
-                       fputs(name, stdout);
-               putchar(line_termination);
+               ce = make_cache_entry(patch->old_mode, sha1_ptr, name, 0, 0);
+               if (add_index_entry(&result, ce, ADD_CACHE_OK_TO_ADD))
+                       die ("Could not add %s to temporary index", name);
        }
+
+       fd = open(filename, O_WRONLY | O_CREAT, 0666);
+       if (fd < 0 || write_index(&result, fd) || close(fd))
+               die ("Could not write temporary index to %s", filename);
+
+       discard_index(&result);
 }
 
 static void stat_patch_list(struct patch *patch)
@@ -2423,7 +2430,6 @@ static void remove_file(struct patch *patch, int rmdir_empty)
        if (update_index) {
                if (remove_file_from_cache(patch->old_name) < 0)
                        die("unable to remove %s from index", patch->old_name);
-               cache_tree_invalidate_path(active_cache_tree, patch->old_name);
        }
        if (!cached) {
                if (S_ISGITLINK(patch->old_mode)) {
@@ -2578,7 +2584,6 @@ static void create_file(struct patch *patch)
                mode = S_IFREG | 0644;
        create_one_file(path, mode, buf, size);
        add_index_file(path, mode, buf, size);
-       cache_tree_invalidate_path(active_cache_tree, path);
 }
 
 /* phase zero is to remove, phase one is to create */
@@ -2805,8 +2810,8 @@ static int apply_patch(int fd, const char *filename, int inaccurate_eof)
        if (apply && write_out_results(list, skipped_patch))
                exit(1);
 
-       if (show_index_info)
-               show_index_list(list);
+       if (fake_ancestor)
+               build_fake_ancestor(list, fake_ancestor);
 
        if (diffstat)
                stat_patch_list(list);
@@ -2914,9 +2919,11 @@ int cmd_apply(int argc, const char **argv, const char *unused_prefix)
                        apply = 1;
                        continue;
                }
-               if (!strcmp(arg, "--index-info")) {
+               if (!strcmp(arg, "--build-fake-ancestor")) {
                        apply = 0;
-                       show_index_info = 1;
+                       if (++i >= argc)
+                               die ("need a filename");
+                       fake_ancestor = argv[i];
                        continue;
                }
                if (!strcmp(arg, "-z")) {
index 187491bc172571b783a0be4f4dfa3d94d58bb0fe..a90c65ce54a1c3e415b46465a2b4c7bfdffca9ff 100644 (file)
@@ -10,6 +10,7 @@
 #include "exec_cmd.h"
 #include "pkt-line.h"
 #include "sideband.h"
+#include "attr.h"
 
 static const char archive_usage[] = \
 "git-archive --format=<fmt> [--prefix=<prefix>/] [--verbose] [<extra>] <tree-ish> [path...]";
@@ -80,6 +81,100 @@ static int run_remote_archiver(const char *remote, int argc,
        return !!rv;
 }
 
+static void *format_subst(const struct commit *commit, const char *format,
+                          unsigned long *sizep)
+{
+       unsigned long len = *sizep, result_len = 0;
+       const char *a = format;
+       char *result = NULL;
+
+       for (;;) {
+               const char *b, *c;
+               char *fmt, *formatted = NULL;
+               unsigned long a_len, fmt_len, formatted_len, allocated = 0;
+
+               b = memmem(a, len, "$Format:", 8);
+               if (!b || a + len < b + 9)
+                       break;
+               c = memchr(b + 8, '$', len - 8);
+               if (!c)
+                       break;
+
+               a_len = b - a;
+               fmt_len = c - b - 8;
+               fmt = xmalloc(fmt_len + 1);
+               memcpy(fmt, b + 8, fmt_len);
+               fmt[fmt_len] = '\0';
+
+               formatted_len = format_commit_message(commit, fmt, &formatted,
+                                                     &allocated);
+               free(fmt);
+               result = xrealloc(result, result_len + a_len + formatted_len);
+               memcpy(result + result_len, a, a_len);
+               memcpy(result + result_len + a_len, formatted, formatted_len);
+               result_len += a_len + formatted_len;
+               len -= c + 1 - a;
+               a = c + 1;
+       }
+
+       if (result && len) {
+               result = xrealloc(result, result_len + len);
+               memcpy(result + result_len, a, len);
+               result_len += len;
+       }
+
+       *sizep = result_len;
+
+       return result;
+}
+
+static void *convert_to_archive(const char *path,
+                                const void *src, unsigned long *sizep,
+                                const struct commit *commit)
+{
+       static struct git_attr *attr_export_subst;
+       struct git_attr_check check[1];
+
+       if (!commit)
+               return NULL;
+
+        if (!attr_export_subst)
+                attr_export_subst = git_attr("export-subst", 12);
+
+       check[0].attr = attr_export_subst;
+       if (git_checkattr(path, ARRAY_SIZE(check), check))
+               return NULL;
+       if (!ATTR_TRUE(check[0].value))
+               return NULL;
+
+       return format_subst(commit, src, sizep);
+}
+
+void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
+                           unsigned int mode, enum object_type *type,
+                           unsigned long *size,
+                           const struct commit *commit)
+{
+       void *buffer, *converted;
+
+       buffer = read_sha1_file(sha1, type, size);
+       if (buffer && S_ISREG(mode)) {
+               converted = convert_to_working_tree(path, buffer, size);
+               if (converted) {
+                       free(buffer);
+                       buffer = converted;
+               }
+
+               converted = convert_to_archive(path, buffer, size, commit);
+               if (converted) {
+                       free(buffer);
+                       buffer = converted;
+               }
+       }
+
+       return buffer;
+}
+
 static int init_archiver(const char *name, struct archiver *ar)
 {
        int rv = -1, i;
@@ -109,7 +204,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
        const unsigned char *commit_sha1;
        time_t archive_time;
        struct tree *tree;
-       struct commit *commit;
+       const struct commit *commit;
        unsigned char sha1[20];
 
        if (get_sha1(name, sha1))
@@ -142,6 +237,7 @@ void parse_treeish_arg(const char **argv, struct archiver_args *ar_args,
        }
        ar_args->tree = tree;
        ar_args->commit_sha1 = commit_sha1;
+       ar_args->commit = commit;
        ar_args->time = archive_time;
 }
 
index e2f8ede9ae4507ed1e431f9d14fc649f6475627d..24c7e6f7dbcee4d02daad7182bb7587c4c9c3418 100644 (file)
@@ -31,24 +31,19 @@ static void show_new(enum object_type type, unsigned char *sha1_new)
                find_unique_abbrev(sha1_new, DEFAULT_ABBREV));
 }
 
-static int update_ref(const char *action,
+static int update_ref_env(const char *action,
                      const char *refname,
                      unsigned char *sha1,
                      unsigned char *oldval)
 {
        char msg[1024];
        char *rla = getenv("GIT_REFLOG_ACTION");
-       static struct ref_lock *lock;
 
        if (!rla)
                rla = "(reflog update)";
-       snprintf(msg, sizeof(msg), "%s: %s", rla, action);
-       lock = lock_any_ref_for_update(refname, oldval, 0);
-       if (!lock)
-               return 1;
-       if (write_ref_sha1(lock, sha1, msg) < 0)
-               return 1;
-       return 0;
+       if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
+               warning("reflog message too long: %.*s...", 50, msg);
+       return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR);
 }
 
 static int update_local_ref(const char *name,
@@ -88,7 +83,7 @@ static int update_local_ref(const char *name,
                fprintf(stderr, "* %s: storing %s\n",
                        name, note);
                show_new(type, sha1_new);
-               return update_ref(msg, name, sha1_new, NULL);
+               return update_ref_env(msg, name, sha1_new, NULL);
        }
 
        if (!hashcmp(sha1_old, sha1_new)) {
@@ -102,7 +97,7 @@ static int update_local_ref(const char *name,
        if (!strncmp(name, "refs/tags/", 10)) {
                fprintf(stderr, "* %s: updating with %s\n", name, note);
                show_new(type, sha1_new);
-               return update_ref("updating tag", name, sha1_new, NULL);
+               return update_ref_env("updating tag", name, sha1_new, NULL);
        }
 
        current = lookup_commit_reference(sha1_old);
@@ -117,7 +112,7 @@ static int update_local_ref(const char *name,
                fprintf(stderr, "* %s: fast forward to %s\n",
                        name, note);
                fprintf(stderr, "  old..new: %s..%s\n", oldh, newh);
-               return update_ref("fast forward", name, sha1_new, sha1_old);
+               return update_ref_env("fast forward", name, sha1_new, sha1_old);
        }
        if (!force) {
                fprintf(stderr,
@@ -131,7 +126,7 @@ static int update_local_ref(const char *name,
                "* %s: forcing update to non-fast forward %s\n",
                name, note);
        fprintf(stderr, "  old...new: %s...%s\n", oldh, newh);
-       return update_ref("forced-update", name, sha1_new, sha1_old);
+       return update_ref_env("forced-update", name, sha1_new, sha1_old);
 }
 
 static int append_fetch_head(FILE *fp,
index 3563216acaebba668f465895fe0563e5d7113fef..b95b7d286ab5358a89a0309927b9f5e5e23fc4d0 100644 (file)
@@ -276,11 +276,8 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
                        add_file_to_cache(path, verbose);
                }
 
-               for (i = 0; i < deleted.nr; i++) {
-                       const char *path = deleted.items[i].path;
-                       remove_file_from_cache(path);
-                       cache_tree_invalidate_path(active_cache_tree, path);
-               }
+               for (i = 0; i < deleted.nr; i++)
+                       remove_file_from_cache(deleted.items[i].path);
 
                if (active_cache_changed) {
                        if (write_cache(newfd, active_cache, active_nr) ||
index 12509faa777bb2903e98c79a98be151380911b87..a15906bdb2021e68a014344cad4e73e9de3367ca 100644 (file)
 #include "list-objects.h"
 #include "progress.h"
 
+#ifdef THREADED_DELTA_SEARCH
+#include <pthread.h>
+#endif
+
 static const char pack_usage[] = "\
 git-pack-objects [{ -q | --progress | --all-progress }] \n\
        [--max-pack-size=N] [--local] [--incremental] \n\
        [--window=N] [--window-memory=N] [--depth=N] \n\
        [--no-reuse-delta] [--no-reuse-object] [--delta-base-offset] \n\
-       [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
+       [--threads=N] [--non-empty] [--revs [--unpacked | --all]*] [--reflog] \n\
        [--stdout | base-name] [<ref-list | <object-list]";
 
 struct object_entry {
@@ -68,6 +72,7 @@ static int progress = 1;
 static int window = 10;
 static uint32_t pack_size_limit;
 static int depth = 50;
+static int delta_search_threads = 1;
 static int pack_to_stdout;
 static int num_preferred_base;
 static struct progress progress_state;
@@ -78,7 +83,6 @@ static unsigned long delta_cache_size = 0;
 static unsigned long max_delta_cache_size = 0;
 static unsigned long cache_max_small_delta_size = 1000;
 
-static unsigned long window_memory_usage = 0;
 static unsigned long window_memory_limit = 0;
 
 /*
@@ -1291,6 +1295,31 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
        return 0;
 }
 
+#ifdef THREADED_DELTA_SEARCH
+
+static pthread_mutex_t read_mutex = PTHREAD_MUTEX_INITIALIZER;
+#define read_lock()            pthread_mutex_lock(&read_mutex)
+#define read_unlock()          pthread_mutex_unlock(&read_mutex)
+
+static pthread_mutex_t cache_mutex = PTHREAD_MUTEX_INITIALIZER;
+#define cache_lock()           pthread_mutex_lock(&cache_mutex)
+#define cache_unlock()         pthread_mutex_unlock(&cache_mutex)
+
+static pthread_mutex_t progress_mutex = PTHREAD_MUTEX_INITIALIZER;
+#define progress_lock()                pthread_mutex_lock(&progress_mutex)
+#define progress_unlock()      pthread_mutex_unlock(&progress_mutex)
+
+#else
+
+#define read_lock()            (void)0
+#define read_unlock()          (void)0
+#define cache_lock()           (void)0
+#define cache_unlock()         (void)0
+#define progress_lock()                (void)0
+#define progress_unlock()      (void)0
+
+#endif
+
 /*
  * We search for deltas _backwards_ in a list sorted by type and
  * by size, so that we see progressively smaller and smaller files.
@@ -1300,7 +1329,7 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size,
  * one.
  */
 static int try_delta(struct unpacked *trg, struct unpacked *src,
-                    unsigned max_depth)
+                    unsigned max_depth, unsigned long *mem_usage)
 {
        struct object_entry *trg_entry = trg->entry;
        struct object_entry *src_entry = src->entry;
@@ -1313,12 +1342,6 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        if (trg_entry->type != src_entry->type)
                return -1;
 
-       /* We do not compute delta to *create* objects we are not
-        * going to pack.
-        */
-       if (trg_entry->preferred_base)
-               return -1;
-
        /*
         * We do not bother to try a delta that we discarded
         * on an earlier try, but only when reusing delta data.
@@ -1355,24 +1378,28 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
 
        /* Load data if not already done */
        if (!trg->data) {
+               read_lock();
                trg->data = read_sha1_file(trg_entry->idx.sha1, &type, &sz);
+               read_unlock();
                if (!trg->data)
                        die("object %s cannot be read",
                            sha1_to_hex(trg_entry->idx.sha1));
                if (sz != trg_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
                            sha1_to_hex(trg_entry->idx.sha1), sz, trg_size);
-               window_memory_usage += sz;
+               *mem_usage += sz;
        }
        if (!src->data) {
+               read_lock();
                src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
+               read_unlock();
                if (!src->data)
                        die("object %s cannot be read",
                            sha1_to_hex(src_entry->idx.sha1));
                if (sz != src_size)
                        die("object %s inconsistent object length (%lu vs %lu)",
                            sha1_to_hex(src_entry->idx.sha1), sz, src_size);
-               window_memory_usage += sz;
+               *mem_usage += sz;
        }
        if (!src->index) {
                src->index = create_delta_index(src->data, src_size);
@@ -1382,7 +1409,7 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
                                warning("suboptimal pack - out of memory");
                        return 0;
                }
-               window_memory_usage += sizeof_delta_index(src->index);
+               *mem_usage += sizeof_delta_index(src->index);
        }
 
        delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size);
@@ -1402,17 +1429,27 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
        trg_entry->delta_size = delta_size;
        trg->depth = src->depth + 1;
 
+       /*
+        * Handle memory allocation outside of the cache
+        * 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);
+       cache_lock();
        if (trg_entry->delta_data) {
                delta_cache_size -= trg_entry->delta_size;
-               free(trg_entry->delta_data);
                trg_entry->delta_data = NULL;
        }
-
        if (delta_cacheable(src_size, trg_size, delta_size)) {
-               trg_entry->delta_data = xrealloc(delta_buf, delta_size);
                delta_cache_size += trg_entry->delta_size;
-       } else
+               cache_unlock();
+               trg_entry->delta_data = xrealloc(delta_buf, delta_size);
+       } else {
+               cache_unlock();
                free(delta_buf);
+       }
+
        return 1;
 }
 
@@ -1429,68 +1466,60 @@ static unsigned int check_delta_limit(struct object_entry *me, unsigned int n)
        return m;
 }
 
-static void free_unpacked(struct unpacked *n)
+static unsigned long free_unpacked(struct unpacked *n)
 {
-       window_memory_usage -= sizeof_delta_index(n->index);
+       unsigned long freed_mem = sizeof_delta_index(n->index);
        free_delta_index(n->index);
        n->index = NULL;
        if (n->data) {
+               freed_mem += n->entry->size;
                free(n->data);
                n->data = NULL;
-               window_memory_usage -= n->entry->size;
        }
        n->entry = NULL;
        n->depth = 0;
+       return freed_mem;
 }
 
-static void find_deltas(struct object_entry **list, int window, int depth)
+static void find_deltas(struct object_entry **list, unsigned list_size,
+                       int window, int depth, unsigned *processed)
 {
-       uint32_t i = nr_objects, idx = 0, count = 0, processed = 0;
+       uint32_t i = list_size, idx = 0, count = 0;
        unsigned int array_size = window * sizeof(struct unpacked);
        struct unpacked *array;
-       int max_depth;
+       unsigned long mem_usage = 0;
 
-       if (!nr_objects)
-               return;
        array = xmalloc(array_size);
        memset(array, 0, array_size);
-       if (progress)
-               start_progress(&progress_state, "Deltifying %u objects...", "", nr_result);
 
        do {
                struct object_entry *entry = list[--i];
                struct unpacked *n = array + idx;
-               int j;
-
-               if (!entry->preferred_base)
-                       processed++;
-
-               if (progress)
-                       display_progress(&progress_state, processed);
+               int j, max_depth, best_base = -1;
 
-               if (entry->delta)
-                       /* This happens if we decided to reuse existing
-                        * delta from a pack.  "!no_reuse_delta &&" is implied.
-                        */
-                       continue;
-
-               if (entry->size < 50)
-                       continue;
-
-               if (entry->no_try_delta)
-                       continue;
-
-               free_unpacked(n);
+               mem_usage -= free_unpacked(n);
                n->entry = entry;
 
                while (window_memory_limit &&
-                      window_memory_usage > window_memory_limit &&
+                      mem_usage > window_memory_limit &&
                       count > 1) {
                        uint32_t tail = (idx + window - count) % window;
-                       free_unpacked(array + tail);
+                       mem_usage -= free_unpacked(array + tail);
                        count--;
                }
 
+               /* We do not compute delta to *create* objects we are not
+                * going to pack.
+                */
+               if (entry->preferred_base)
+                       goto next;
+
+               progress_lock();
+               (*processed)++;
+               if (progress)
+                       display_progress(&progress_state, *processed);
+               progress_unlock();
+
                /*
                 * If the current object is at pack edge, take the depth the
                 * objects that depend on the current object into account
@@ -1505,6 +1534,7 @@ static void find_deltas(struct object_entry **list, int window, int depth)
 
                j = window;
                while (--j > 0) {
+                       int ret;
                        uint32_t other_idx = idx + j;
                        struct unpacked *m;
                        if (other_idx >= window)
@@ -1512,8 +1542,11 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        m = array + other_idx;
                        if (!m->entry)
                                break;
-                       if (try_delta(n, m, max_depth) < 0)
+                       ret = try_delta(n, m, max_depth, &mem_usage);
+                       if (ret < 0)
                                break;
+                       else if (ret > 0)
+                               best_base = other_idx;
                }
 
                /* if we made n a delta, and if n is already at max
@@ -1523,6 +1556,23 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                if (entry->delta && depth <= n->depth)
                        continue;
 
+               /*
+                * Move the best delta base up in the window, after the
+                * currently deltified object, to keep it longer.  It will
+                * be the first base object to be attempted next.
+                */
+               if (entry->delta) {
+                       struct unpacked swap = array[best_base];
+                       int dist = (window + idx - best_base) % window;
+                       int dst = best_base;
+                       while (dist--) {
+                               int src = (dst + 1) % window;
+                               array[dst] = array[src];
+                               dst = src;
+                       }
+                       array[dst] = swap;
+               }
+
                next:
                idx++;
                if (count + 1 < window)
@@ -1531,9 +1581,6 @@ static void find_deltas(struct object_entry **list, int window, int depth)
                        idx = 0;
        } while (i > 0);
 
-       if (progress)
-               stop_progress(&progress_state);
-
        for (i = 0; i < window; ++i) {
                free_delta_index(array[i].index);
                free(array[i].data);
@@ -1541,21 +1588,145 @@ static void find_deltas(struct object_entry **list, int window, int depth)
        free(array);
 }
 
+#ifdef THREADED_DELTA_SEARCH
+
+struct thread_params {
+       pthread_t thread;
+       struct object_entry **list;
+       unsigned list_size;
+       int window;
+       int depth;
+       unsigned *processed;
+};
+
+static pthread_mutex_t data_request  = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t data_ready    = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t data_provider = PTHREAD_MUTEX_INITIALIZER;
+static struct thread_params *data_requester;
+
+static void *threaded_find_deltas(void *arg)
+{
+       struct thread_params *me = arg;
+
+       for (;;) {
+               pthread_mutex_lock(&data_request);
+               data_requester = me;
+               pthread_mutex_unlock(&data_provider);
+               pthread_mutex_lock(&data_ready);
+               pthread_mutex_unlock(&data_request);
+
+               if (!me->list_size)
+                       return NULL;
+
+               find_deltas(me->list, me->list_size,
+                           me->window, me->depth, me->processed);
+       }
+}
+
+static void ll_find_deltas(struct object_entry **list, unsigned list_size,
+                          int window, int depth, unsigned *processed)
+{
+       struct thread_params *target, p[delta_search_threads];
+       int i, ret;
+       unsigned chunk_size;
+
+       if (delta_search_threads <= 1) {
+               find_deltas(list, list_size, window, depth, processed);
+               return;
+       }
+
+       pthread_mutex_lock(&data_provider);
+       pthread_mutex_lock(&data_ready);
+
+       for (i = 0; i < delta_search_threads; i++) {
+               p[i].window = window;
+               p[i].depth = depth;
+               p[i].processed = processed;
+               ret = pthread_create(&p[i].thread, NULL,
+                                    threaded_find_deltas, &p[i]);
+               if (ret)
+                       die("unable to create thread: %s", strerror(ret));
+       }
+
+       /* this should be auto-tuned somehow */
+       chunk_size = window * 1000;
+
+       do {
+               unsigned sublist_size = chunk_size;
+               if (sublist_size > list_size)
+                       sublist_size = list_size;
+
+               /* try to split chunks on "path" boundaries */
+               while (sublist_size < list_size && list[sublist_size]->hash &&
+                      list[sublist_size]->hash == list[sublist_size-1]->hash)
+                       sublist_size++;
+
+               pthread_mutex_lock(&data_provider);
+               target = data_requester;
+               target->list = list;
+               target->list_size = sublist_size;
+               pthread_mutex_unlock(&data_ready);
+
+               list += sublist_size;
+               list_size -= sublist_size;
+               if (!sublist_size) {
+                       pthread_join(target->thread, NULL);
+                       i--;
+               }
+       } while (i);
+}
+
+#else
+#define ll_find_deltas find_deltas
+#endif
+
 static void prepare_pack(int window, int depth)
 {
        struct object_entry **delta_list;
-       uint32_t i;
+       uint32_t i, n, nr_deltas;
 
        get_object_details();
 
-       if (!window || !depth)
+       if (!nr_objects || !window || !depth)
                return;
 
        delta_list = xmalloc(nr_objects * sizeof(*delta_list));
-       for (i = 0; i < nr_objects; i++)
-               delta_list[i] = objects + i;
-       qsort(delta_list, nr_objects, sizeof(*delta_list), type_size_sort);
-       find_deltas(delta_list, window+1, depth);
+       nr_deltas = n = 0;
+
+       for (i = 0; i < nr_objects; i++) {
+               struct object_entry *entry = objects + i;
+
+               if (entry->delta)
+                       /* This happens if we decided to reuse existing
+                        * delta from a pack.  "!no_reuse_delta &&" is implied.
+                        */
+                       continue;
+
+               if (entry->size < 50)
+                       continue;
+
+               if (entry->no_try_delta)
+                       continue;
+
+               if (!entry->preferred_base)
+                       nr_deltas++;
+
+               delta_list[n++] = entry;
+       }
+
+       if (nr_deltas) {
+               unsigned nr_done = 0;
+               if (progress)
+                       start_progress(&progress_state,
+                                      "Deltifying %u objects...", "",
+                                      nr_deltas);
+               qsort(delta_list, n, sizeof(*delta_list), type_size_sort);
+               ll_find_deltas(delta_list, n, window+1, depth, &nr_done);
+               if (progress)
+                       stop_progress(&progress_state);
+               if (nr_done != nr_deltas)
+                       die("inconsistency with delta count");
+       }
        free(delta_list);
 }
 
@@ -1591,6 +1762,17 @@ static int git_pack_config(const char *k, const char *v)
                cache_max_small_delta_size = git_config_int(k, v);
                return 0;
        }
+       if (!strcmp(k, "pack.threads")) {
+               delta_search_threads = git_config_int(k, v);
+               if (delta_search_threads < 1)
+                       die("invalid number of threads specified (%d)",
+                           delta_search_threads);
+#ifndef THREADED_DELTA_SEARCH
+               if (delta_search_threads > 1)
+                       warning("no threads support, ignoring %s", k);
+#endif
+               return 0;
+       }
        return git_default_config(k, v);
 }
 
@@ -1750,6 +1932,18 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix)
                                usage(pack_usage);
                        continue;
                }
+               if (!prefixcmp(arg, "--threads=")) {
+                       char *end;
+                       delta_search_threads = strtoul(arg+10, &end, 0);
+                       if (!arg[10] || *end || delta_search_threads < 1)
+                               usage(pack_usage);
+#ifndef THREADED_DELTA_SEARCH
+                       if (delta_search_threads > 1)
+                               warning("no threads support, "
+                                       "ignoring %s", arg);
+#endif
+                       continue;
+               }
                if (!prefixcmp(arg, "--depth=")) {
                        char *end;
                        depth = strtoul(arg+8, &end, 0);
diff --git a/builtin-reset.c b/builtin-reset.c
new file mode 100644 (file)
index 0000000..99d5c08
--- /dev/null
@@ -0,0 +1,279 @@
+/*
+ * "git reset" builtin command
+ *
+ * Copyright (c) 2007 Carlos Rica
+ *
+ * Based on git-reset.sh, which is
+ *
+ * Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+ */
+#include "cache.h"
+#include "tag.h"
+#include "object.h"
+#include "commit.h"
+#include "run-command.h"
+#include "refs.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "tree.h"
+
+static const char builtin_reset_usage[] =
+"git-reset [--mixed | --soft | --hard]  [<commit-ish>] [ [--] <paths>...]";
+
+static char *args_to_str(const char **argv)
+{
+       char *buf = NULL;
+       unsigned long len, space = 0, nr = 0;
+
+       for (; *argv; argv++) {
+               len = strlen(*argv);
+               ALLOC_GROW(buf, nr + 1 + len, space);
+               if (nr)
+                       buf[nr++] = ' ';
+               memcpy(buf + nr, *argv, len);
+               nr += len;
+       }
+       ALLOC_GROW(buf, nr + 1, space);
+       buf[nr] = '\0';
+
+       return buf;
+}
+
+static inline int is_merge(void)
+{
+       return !access(git_path("MERGE_HEAD"), F_OK);
+}
+
+static int unmerged_files(void)
+{
+       char b;
+       ssize_t len;
+       struct child_process cmd;
+       const char *argv_ls_files[] = {"ls-files", "--unmerged", NULL};
+
+       memset(&cmd, 0, sizeof(cmd));
+       cmd.argv = argv_ls_files;
+       cmd.git_cmd = 1;
+       cmd.out = -1;
+
+       if (start_command(&cmd))
+               die("Could not run sub-command: git ls-files");
+
+       len = xread(cmd.out, &b, 1);
+       if (len < 0)
+               die("Could not read output from git ls-files: %s",
+                                               strerror(errno));
+       finish_command(&cmd);
+
+       return len;
+}
+
+static int reset_index_file(const unsigned char *sha1, int is_hard_reset)
+{
+       int i = 0;
+       const char *args[6];
+
+       args[i++] = "read-tree";
+       args[i++] = "-v";
+       args[i++] = "--reset";
+       if (is_hard_reset)
+               args[i++] = "-u";
+       args[i++] = sha1_to_hex(sha1);
+       args[i] = NULL;
+
+       return run_command_v_opt(args, RUN_GIT_CMD);
+}
+
+static void print_new_head_line(struct commit *commit)
+{
+       const char *hex, *dots = "...", *body;
+
+       hex = find_unique_abbrev(commit->object.sha1, DEFAULT_ABBREV);
+       if (!hex) {
+               hex = sha1_to_hex(commit->object.sha1);
+               dots = "";
+       }
+       printf("HEAD is now at %s%s", hex, dots);
+       body = strstr(commit->buffer, "\n\n");
+       if (body) {
+               const char *eol;
+               size_t len;
+               body += 2;
+               eol = strchr(body, '\n');
+               len = eol ? eol - body : strlen(body);
+               printf(" %.*s\n", (int) len, body);
+       }
+       else
+               printf("\n");
+}
+
+static int update_index_refresh(void)
+{
+       const char *argv_update_index[] = {"update-index", "--refresh", NULL};
+       return run_command_v_opt(argv_update_index, RUN_GIT_CMD);
+}
+
+static void update_index_from_diff(struct diff_queue_struct *q,
+               struct diff_options *opt, void *data)
+{
+       int i;
+
+       /* do_diff_cache() mangled the index */
+       discard_cache();
+       read_cache();
+
+       for (i = 0; i < q->nr; i++) {
+               struct diff_filespec *one = q->queue[i]->one;
+               if (one->mode) {
+                       struct cache_entry *ce;
+                       ce = make_cache_entry(one->mode, one->sha1, one->path,
+                               0, 0);
+                       add_cache_entry(ce, ADD_CACHE_OK_TO_ADD |
+                               ADD_CACHE_OK_TO_REPLACE);
+               } else
+                       remove_file_from_cache(one->path);
+       }
+}
+
+static int read_from_tree(const char *prefix, const char **argv,
+               unsigned char *tree_sha1)
+{
+        struct lock_file *lock = xcalloc(1, sizeof(struct lock_file));
+       int index_fd;
+       struct diff_options opt;
+
+       memset(&opt, 0, sizeof(opt));
+       diff_tree_setup_paths(get_pathspec(prefix, (const char **)argv), &opt);
+       opt.output_format = DIFF_FORMAT_CALLBACK;
+       opt.format_callback = update_index_from_diff;
+
+       index_fd = hold_locked_index(lock, 1);
+       read_cache();
+       if (do_diff_cache(tree_sha1, &opt))
+               return 1;
+       diffcore_std(&opt);
+       diff_flush(&opt);
+       return write_cache(index_fd, active_cache, active_nr) ||
+               close(index_fd) ||
+               commit_locked_index(lock);
+}
+
+static void prepend_reflog_action(const char *action, char *buf, size_t size)
+{
+       const char *sep = ": ";
+       const char *rla = getenv("GIT_REFLOG_ACTION");
+       if (!rla)
+               rla = sep = "";
+       if (snprintf(buf, size, "%s%s%s", rla, sep, action) >= size)
+               warning("Reflog action message too long: %.*s...", 50, buf);
+}
+
+enum reset_type { MIXED, SOFT, HARD, NONE };
+static char *reset_type_names[] = { "mixed", "soft", "hard", NULL };
+
+int cmd_reset(int argc, const char **argv, const char *prefix)
+{
+       int i = 1, reset_type = NONE, update_ref_status = 0;
+       const char *rev = "HEAD";
+       unsigned char sha1[20], *orig = NULL, sha1_orig[20],
+                               *old_orig = NULL, sha1_old_orig[20];
+       struct commit *commit;
+       char *reflog_action, msg[1024];
+
+       git_config(git_default_config);
+
+       reflog_action = args_to_str(argv);
+       setenv("GIT_REFLOG_ACTION", reflog_action, 0);
+
+       if (i < argc) {
+               if (!strcmp(argv[i], "--mixed")) {
+                       reset_type = MIXED;
+                       i++;
+               }
+               else if (!strcmp(argv[i], "--soft")) {
+                       reset_type = SOFT;
+                       i++;
+               }
+               else if (!strcmp(argv[i], "--hard")) {
+                       reset_type = HARD;
+                       i++;
+               }
+       }
+
+       if (i < argc && argv[i][0] != '-')
+               rev = argv[i++];
+
+       if (get_sha1(rev, sha1))
+               die("Failed to resolve '%s' as a valid ref.", rev);
+
+       commit = lookup_commit_reference(sha1);
+       if (!commit)
+               die("Could not parse object '%s'.", rev);
+       hashcpy(sha1, commit->object.sha1);
+
+       if (i < argc && !strcmp(argv[i], "--"))
+               i++;
+       else if (i < argc && argv[i][0] == '-')
+               usage(builtin_reset_usage);
+
+       /* git reset tree [--] paths... can be used to
+        * load chosen paths from the tree into the index without
+        * affecting the working tree nor HEAD. */
+       if (i < argc) {
+               if (reset_type == MIXED)
+                       warning("--mixed option is deprecated with paths.");
+               else if (reset_type != NONE)
+                       die("Cannot do %s reset with paths.",
+                                       reset_type_names[reset_type]);
+               if (read_from_tree(prefix, argv + i, sha1))
+                       return 1;
+               return update_index_refresh() ? 1 : 0;
+       }
+       if (reset_type == NONE)
+               reset_type = MIXED; /* by default */
+
+       /* Soft reset does not touch the index file nor the working tree
+        * 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())
+                       die("Cannot do a soft reset in the middle of a merge.");
+       }
+       else if (reset_index_file(sha1, (reset_type == HARD)))
+               die("Could not reset index file to revision '%s'.", rev);
+
+       /* Any resets update HEAD to the head being switched to,
+        * saving the previous head in ORIG_HEAD before. */
+       if (!get_sha1("ORIG_HEAD", sha1_old_orig))
+               old_orig = sha1_old_orig;
+       if (!get_sha1("HEAD", sha1_orig)) {
+               orig = sha1_orig;
+               prepend_reflog_action("updating ORIG_HEAD", msg, sizeof(msg));
+               update_ref(msg, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+       }
+       else if (old_orig)
+               delete_ref("ORIG_HEAD", old_orig);
+       prepend_reflog_action("updating HEAD", msg, sizeof(msg));
+       update_ref_status = update_ref(msg, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+
+       switch (reset_type) {
+       case HARD:
+               if (!update_ref_status)
+                       print_new_head_line(commit);
+               break;
+       case SOFT: /* Nothing else to do. */
+               break;
+       case MIXED: /* Report what has not been updated. */
+               update_index_refresh();
+               break;
+       }
+
+       unlink(git_path("MERGE_HEAD"));
+       unlink(git_path("rr-cache/MERGE_RR"));
+       unlink(git_path("MERGE_MSG"));
+       unlink(git_path("SQUASH_MSG"));
+
+       free(reflog_action);
+
+       return update_ref_status;
+}
index ac551d59f3f6cb209a645ad708c46e4e88791295..38946339999e4e136b898b8f314e27d22ec1decb 100644 (file)
@@ -189,7 +189,7 @@ static int count_interesting_parents(struct commit *commit)
        return count;
 }
 
-static inline int halfway(struct commit_list *p, int distance, int nr)
+static inline int halfway(struct commit_list *p, int nr)
 {
        /*
         * Don't short-cut something we are not going to return!
@@ -202,8 +202,7 @@ static inline int halfway(struct commit_list *p, int distance, int nr)
         * 2 and 3 are halfway of 5.
         * 3 is halfway of 6 but 2 and 4 are not.
         */
-       distance *= 2;
-       switch (distance - nr) {
+       switch (2 * weight(p) - nr) {
        case -1: case 0: case 1:
                return 1;
        default:
@@ -255,6 +254,30 @@ static void show_list(const char *debug, int counted, int nr,
 }
 #endif /* DEBUG_BISECT */
 
+static struct commit_list *best_bisection(struct commit_list *list, int nr)
+{
+       struct commit_list *p, *best;
+       int best_distance = -1;
+
+       best = list;
+       for (p = list; p; p = p->next) {
+               int distance;
+               unsigned flags = p->item->object.flags;
+
+               if (revs.prune_fn && !(flags & TREECHANGE))
+                       continue;
+               distance = weight(p);
+               if (nr - distance < distance)
+                       distance = nr - distance;
+               if (distance > best_distance) {
+                       best = p;
+                       best_distance = distance;
+               }
+       }
+
+       return best;
+}
+
 /*
  * zero or positive weight is the number of interesting commits it can
  * reach, including itself.  Especially, weight = 0 means it does not
@@ -268,39 +291,12 @@ static void show_list(const char *debug, int counted, int nr,
  * unknown.  After running count_distance() first, they will get zero
  * or positive distance.
  */
-
-static struct commit_list *find_bisection(struct commit_list *list,
-                                         int *reaches, int *all)
+static struct commit_list *do_find_bisection(struct commit_list *list,
+                                            int nr, int *weights)
 {
-       int n, nr, on_list, counted, distance;
-       struct commit_list *p, *best, *next, *last;
-       int *weights;
-
-       show_list("bisection 2 entry", 0, 0, list);
-
-       /*
-        * Count the number of total and tree-changing items on the
-        * list, while reversing the list.
-        */
-       for (nr = on_list = 0, last = NULL, p = list;
-            p;
-            p = next) {
-               unsigned flags = p->item->object.flags;
-
-               next = p->next;
-               if (flags & UNINTERESTING)
-                       continue;
-               p->next = last;
-               last = p;
-               if (!revs.prune_fn || (flags & TREECHANGE))
-                       nr++;
-               on_list++;
-       }
-       list = last;
-       show_list("bisection 2 sorted", 0, nr, list);
+       int n, counted;
+       struct commit_list *p;
 
-       *all = nr;
-       weights = xcalloc(on_list, sizeof(*weights));
        counted = 0;
 
        for (n = 0, p = list; p; p = p->next) {
@@ -349,20 +345,14 @@ static struct commit_list *find_bisection(struct commit_list *list,
        for (p = list; p; p = p->next) {
                if (p->item->object.flags & UNINTERESTING)
                        continue;
-               n = weight(p);
-               if (n != -2)
+               if (weight(p) != -2)
                        continue;
-               distance = count_distance(p);
+               weight_set(p, count_distance(p));
                clear_distance(list);
-               weight_set(p, distance);
 
                /* Does it happen to be at exactly half-way? */
-               if (halfway(p, distance, nr)) {
-                       p->next = NULL;
-                       *reaches = distance;
-                       free(weights);
+               if (halfway(p, nr))
                        return p;
-               }
                counted++;
        }
 
@@ -399,38 +389,59 @@ static struct commit_list *find_bisection(struct commit_list *list,
                                weight_set(p, weight(q));
 
                        /* Does it happen to be at exactly half-way? */
-                       distance = weight(p);
-                       if (halfway(p, distance, nr)) {
-                               p->next = NULL;
-                               *reaches = distance;
-                               free(weights);
+                       if (halfway(p, nr))
                                return p;
-                       }
                }
        }
 
        show_list("bisection 2 counted all", counted, nr, list);
 
        /* Then find the best one */
-       counted = -1;
-       best = list;
-       for (p = list; p; p = p->next) {
+       return best_bisection(list, nr);
+}
+
+static struct commit_list *find_bisection(struct commit_list *list,
+                                         int *reaches, int *all)
+{
+       int nr, on_list;
+       struct commit_list *p, *best, *next, *last;
+       int *weights;
+
+       show_list("bisection 2 entry", 0, 0, list);
+
+       /*
+        * Count the number of total and tree-changing items on the
+        * list, while reversing the list.
+        */
+       for (nr = on_list = 0, last = NULL, p = list;
+            p;
+            p = next) {
                unsigned flags = p->item->object.flags;
 
-               if (revs.prune_fn && !(flags & TREECHANGE))
+               next = p->next;
+               if (flags & UNINTERESTING)
                        continue;
-               distance = weight(p);
-               if (nr - distance < distance)
-                       distance = nr - distance;
-               if (distance > counted) {
-                       best = p;
-                       counted = distance;
-                       *reaches = weight(p);
-               }
+               p->next = last;
+               last = p;
+               if (!revs.prune_fn || (flags & TREECHANGE))
+                       nr++;
+               on_list++;
        }
-       if (best)
+       list = last;
+       show_list("bisection 2 sorted", 0, nr, list);
+
+       *all = nr;
+       weights = xcalloc(on_list, sizeof(*weights));
+
+       /* Do the real work of finding bisection commit. */
+       best = do_find_bisection(list, nr, weights);
+
+       if (best) {
                best->next = NULL;
+               *reaches = weight(best);
+       }
        free(weights);
+
        return best;
 }
 
index 9a808c1bf96ec52d836b0a69816f6118f0291a45..3b0677e44b290f0a44fd81bd2e31ddc96bc1946d 100644 (file)
@@ -227,7 +227,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix)
 
                if (remove_file_from_cache(path))
                        die("git-rm: unable to remove %s", path);
-               cache_tree_invalidate_path(active_cache_tree, path);
        }
 
        if (show_only)
index a7a4574f2bff2a7db4a1c25aa4a514ad99760381..55fb679d68f141253f5d0ba5c73033d267e2a271 100644 (file)
@@ -195,11 +195,6 @@ static int process_path(const char *path)
        int len;
        struct stat st;
 
-       /* We probably want to do this in remove_file_from_cache() and
-        * add_cache_entry() instead...
-        */
-       cache_tree_invalidate_path(active_cache_tree, path);
-
        /*
         * First things first: get the stat information, to decide
         * what to do about the pathname!
@@ -239,7 +234,6 @@ static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                return error("%s: cannot add to the index - missing --add option?",
                             path);
        report("add '%s'", path);
-       cache_tree_invalidate_path(active_cache_tree, path);
        return 0;
 }
 
@@ -284,7 +278,6 @@ static void update_one(const char *path, const char *prefix, int prefix_length)
                        die("Unable to mark file %s", path);
                goto free_return;
        }
-       cache_tree_invalidate_path(active_cache_tree, path);
 
        if (force_remove) {
                if (remove_file_from_cache(p))
@@ -367,7 +360,6 @@ static void read_index_info(int line_termination)
                                free(path_name);
                        continue;
                }
-               cache_tree_invalidate_path(active_cache_tree, path_name);
 
                if (!mode) {
                        /* mode == 0 means there is no such path -- remove */
@@ -474,7 +466,6 @@ static int unresolve_one(const char *path)
                goto free_return;
        }
 
-       cache_tree_invalidate_path(active_cache_tree, path);
        remove_file_from_cache(path);
        if (add_cache_entry(ce_2, ADD_CACHE_OK_TO_ADD)) {
                error("%s: cannot add our version to the index.", path);
index 8339cf19e2b68a19e88c1a014c52672bc6f0c7b5..fe1f74c9f3fa4a44cfd56508171e65a40e27342c 100644 (file)
@@ -8,7 +8,6 @@ static const char git_update_ref_usage[] =
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
        const char *refname=NULL, *value=NULL, *oldval=NULL, *msg=NULL;
-       struct ref_lock *lock;
        unsigned char sha1[20], oldsha1[20];
        int i, delete, ref_flags;
 
@@ -62,10 +61,6 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        if (oldval && *oldval && get_sha1(oldval, oldsha1))
                die("%s: not a valid old SHA1", oldval);
 
-       lock = lock_any_ref_for_update(refname, oldval ? oldsha1 : NULL, ref_flags);
-       if (!lock)
-               die("%s: cannot lock the ref", refname);
-       if (write_ref_sha1(lock, sha1, msg) < 0)
-               die("%s: cannot update the ref", refname);
-       return 0;
+       return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
+                         ref_flags, DIE_ON_ERR);
 }
index dfcfcd0455ce471a6120d11bcbdb7553de59f536..cc4c55d7ee35ceeaf8c4a857d5dad857a7eb2664 100644 (file)
@@ -35,7 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
 
        /* find the length without signature */
        len = 0;
-       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE "\n")) {
+       while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
                eol = memchr(buf + len, '\n', size - len);
                len += eol ? eol - (buf + len) + 1 : size - len;
        }
index bb720004afeb632005a5622ecb9cd25f95f5caa5..03ee7bf780be93601f9190b733984ad82951952b 100644 (file)
--- a/builtin.h
+++ b/builtin.h
@@ -60,6 +60,7 @@ extern int cmd_read_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_reflog(int argc, const char **argv, const char *prefix);
 extern int cmd_config(int argc, const char **argv, const char *prefix);
 extern int cmd_rerere(int argc, const char **argv, const char *prefix);
+extern int cmd_reset(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_list(int argc, const char **argv, const char *prefix);
 extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_revert(int argc, const char **argv, const char *prefix);
diff --git a/cache.h b/cache.h
index 70abbd59bf8d3ca118836605099b0b5dc66a27b7..824650016677353cfa8c8a140eb3d904f56d60ee 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -264,6 +264,7 @@ extern struct cache_entry *refresh_cache_entry(struct cache_entry *ce, int reall
 extern int remove_index_entry_at(struct index_state *, int pos);
 extern int remove_file_from_index(struct index_state *, const char *path);
 extern int add_file_to_index(struct index_state *, const char *path, int verbose);
+extern struct cache_entry *make_cache_entry(unsigned int mode, const unsigned char *sha1, const char *path, int stage, int refresh);
 extern int ce_same_name(struct cache_entry *a, struct cache_entry *b);
 extern int ie_match_stat(struct index_state *, struct cache_entry *, struct stat *, int);
 extern int ie_modified(struct index_state *, struct cache_entry *, struct stat *, int);
@@ -592,7 +593,6 @@ extern void trace_argv_printf(const char **argv, int count, const char *format,
 /* convert.c */
 extern char *convert_to_git(const char *path, const char *src, unsigned long *sizep);
 extern char *convert_to_working_tree(const char *path, const char *src, unsigned long *sizep);
-extern void *convert_sha1_file(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size);
 
 /* diff.c */
 extern int diff_auto_refresh_index;
index dc5a0643f3d52797f29706595355fca824d9feda..99f65cee0e7e30e833f850b5993aa8a5797ba889 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -787,8 +787,8 @@ static void fill_person(struct interp *table, const char *msg, int len)
        interp_set_entry(table, 6, show_date(date, tz, DATE_ISO8601));
 }
 
-static long format_commit_message(const struct commit *commit,
-               const char *msg, char **buf_p, unsigned long *space_p)
+long format_commit_message(const struct commit *commit, const void *format,
+                           char **buf_p, unsigned long *space_p)
 {
        struct interp table[] = {
                { "%H" },       /* commit hash */
@@ -843,6 +843,7 @@ static long format_commit_message(const struct commit *commit,
        char parents[1024];
        int i;
        enum { HEADER, SUBJECT, BODY } state;
+       const char *msg = commit->buffer;
 
        if (ILEFT_RIGHT + 1 != ARRAY_SIZE(table))
                die("invalid interp table!");
@@ -924,7 +925,7 @@ static long format_commit_message(const struct commit *commit,
                char *buf = *buf_p;
                unsigned long space = *space_p;
 
-               space = interpolate(buf, space, user_format,
+               space = interpolate(buf, space, format,
                                    table, ARRAY_SIZE(table));
                if (!space)
                        break;
@@ -1165,7 +1166,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt,
        char *buf;
 
        if (fmt == CMIT_FMT_USERFORMAT)
-               return format_commit_message(commit, msg, buf_p, space_p);
+               return format_commit_message(commit, user_format, buf_p, space_p);
 
        encoding = (git_log_output_encoding
                    ? git_log_output_encoding
index 467872eecabf05ccedbb9bf8247b6de244416b8f..a8d76616d2ae6965ebca3c197232e1b7c9294585 100644 (file)
--- a/commit.h
+++ b/commit.h
@@ -61,6 +61,7 @@ enum cmit_fmt {
 };
 
 extern enum cmit_fmt get_commit_format(const char *arg);
+extern long format_commit_message(const struct commit *commit, const void *template, char **buf_p, unsigned long *space_p);
 extern unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *, unsigned long len, char **buf_p, unsigned long *space_p, int abbrev, const char *subject, const char *after_subject, enum date_mode dmode);
 
 /** Removes the first commit from a list sorted by date, and adds all
diff --git a/compat/memmem.c b/compat/memmem.c
new file mode 100644 (file)
index 0000000..cd0d877
--- /dev/null
@@ -0,0 +1,29 @@
+#include "../git-compat-util.h"
+
+void *gitmemmem(const void *haystack, size_t haystack_len,
+                const void *needle, size_t needle_len)
+{
+       const char *begin = haystack;
+       const char *last_possible = begin + haystack_len - needle_len;
+
+       /*
+        * The first occurrence of the empty string is deemed to occur at
+        * the beginning of the string.
+        */
+       if (needle_len == 0)
+               return (void *)begin;
+
+       /*
+        * Sanity check, otherwise the loop might search through the whole
+        * memory.
+        */
+       if (haystack_len < needle_len)
+               return NULL;
+
+       for (; begin <= last_possible; begin++) {
+               if (!memcmp(begin, needle, needle_len))
+                       return (void *)begin;
+       }
+
+       return NULL;
+}
index cad842af4548f24041aba785f1629081e586e7c5..e76093074035d4b9aa4d41e993b5c29e359dad53 100755 (executable)
@@ -299,7 +299,6 @@ __git_commands ()
                check-attr)       : plumbing;;
                check-ref-format) : plumbing;;
                commit-tree)      : plumbing;;
-               convert-objects)  : plumbing;;
                cvsexportcommit)  : export;;
                cvsimport)        : import;;
                cvsserver)        : daemon;;
diff --git a/contrib/convert-objects/convert-objects.c b/contrib/convert-objects/convert-objects.c
new file mode 100644 (file)
index 0000000..90e7900
--- /dev/null
@@ -0,0 +1,329 @@
+#include "cache.h"
+#include "blob.h"
+#include "commit.h"
+#include "tree.h"
+
+struct entry {
+       unsigned char old_sha1[20];
+       unsigned char new_sha1[20];
+       int converted;
+};
+
+#define MAXOBJECTS (1000000)
+
+static struct entry *convert[MAXOBJECTS];
+static int nr_convert;
+
+static struct entry * convert_entry(unsigned char *sha1);
+
+static struct entry *insert_new(unsigned char *sha1, int pos)
+{
+       struct entry *new = xcalloc(1, sizeof(struct entry));
+       hashcpy(new->old_sha1, sha1);
+       memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
+       convert[pos] = new;
+       nr_convert++;
+       if (nr_convert == MAXOBJECTS)
+               die("you're kidding me - hit maximum object limit");
+       return new;
+}
+
+static struct entry *lookup_entry(unsigned char *sha1)
+{
+       int low = 0, high = nr_convert;
+
+       while (low < high) {
+               int next = (low + high) / 2;
+               struct entry *n = convert[next];
+               int cmp = hashcmp(sha1, n->old_sha1);
+               if (!cmp)
+                       return n;
+               if (cmp < 0) {
+                       high = next;
+                       continue;
+               }
+               low = next+1;
+       }
+       return insert_new(sha1, low);
+}
+
+static void convert_binary_sha1(void *buffer)
+{
+       struct entry *entry = convert_entry(buffer);
+       hashcpy(buffer, entry->new_sha1);
+}
+
+static void convert_ascii_sha1(void *buffer)
+{
+       unsigned char sha1[20];
+       struct entry *entry;
+
+       if (get_sha1_hex(buffer, sha1))
+               die("expected sha1, got '%s'", (char*) buffer);
+       entry = convert_entry(sha1);
+       memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
+}
+
+static unsigned int convert_mode(unsigned int mode)
+{
+       unsigned int newmode;
+
+       newmode = mode & S_IFMT;
+       if (S_ISREG(mode))
+               newmode |= (mode & 0100) ? 0755 : 0644;
+       return newmode;
+}
+
+static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
+{
+       char *new = xmalloc(size);
+       unsigned long newlen = 0;
+       unsigned long used;
+
+       used = 0;
+       while (size) {
+               int len = 21 + strlen(buffer);
+               char *path = strchr(buffer, ' ');
+               unsigned char *sha1;
+               unsigned int mode;
+               char *slash, *origpath;
+
+               if (!path || strtoul_ui(buffer, 8, &mode))
+                       die("bad tree conversion");
+               mode = convert_mode(mode);
+               path++;
+               if (memcmp(path, base, baselen))
+                       break;
+               origpath = path;
+               path += baselen;
+               slash = strchr(path, '/');
+               if (!slash) {
+                       newlen += sprintf(new + newlen, "%o %s", mode, path);
+                       new[newlen++] = '\0';
+                       hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20);
+                       newlen += 20;
+
+                       used += len;
+                       size -= len;
+                       buffer = (char *) buffer + len;
+                       continue;
+               }
+
+               newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
+               new[newlen++] = 0;
+               sha1 = (unsigned char *)(new + newlen);
+               newlen += 20;
+
+               len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
+
+               used += len;
+               size -= len;
+               buffer = (char *) buffer + len;
+       }
+
+       write_sha1_file(new, newlen, tree_type, result_sha1);
+       free(new);
+       return used;
+}
+
+static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       void *orig_buffer = buffer;
+       unsigned long orig_size = size;
+
+       while (size) {
+               size_t len = 1+strlen(buffer);
+
+               convert_binary_sha1((char *) buffer + len);
+
+               len += 20;
+               if (len > size)
+                       die("corrupt tree object");
+               size -= len;
+               buffer = (char *) buffer + len;
+       }
+
+       write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
+}
+
+static unsigned long parse_oldstyle_date(const char *buf)
+{
+       char c, *p;
+       char buffer[100];
+       struct tm tm;
+       const char *formats[] = {
+               "%c",
+               "%a %b %d %T",
+               "%Z",
+               "%Y",
+               " %Y",
+               NULL
+       };
+       /* We only ever did two timezones in the bad old format .. */
+       const char *timezones[] = {
+               "PDT", "PST", "CEST", NULL
+       };
+       const char **fmt = formats;
+
+       p = buffer;
+       while (isspace(c = *buf))
+               buf++;
+       while ((c = *buf++) != '\n')
+               *p++ = c;
+       *p++ = 0;
+       buf = buffer;
+       memset(&tm, 0, sizeof(tm));
+       do {
+               const char *next = strptime(buf, *fmt, &tm);
+               if (next) {
+                       if (!*next)
+                               return mktime(&tm);
+                       buf = next;
+               } else {
+                       const char **p = timezones;
+                       while (isspace(*buf))
+                               buf++;
+                       while (*p) {
+                               if (!memcmp(buf, *p, strlen(*p))) {
+                                       buf += strlen(*p);
+                                       break;
+                               }
+                               p++;
+                       }
+               }
+               fmt++;
+       } while (*buf && *fmt);
+       printf("left: %s\n", buf);
+       return mktime(&tm);
+}
+
+static int convert_date_line(char *dst, void **buf, unsigned long *sp)
+{
+       unsigned long size = *sp;
+       char *line = *buf;
+       char *next = strchr(line, '\n');
+       char *date = strchr(line, '>');
+       int len;
+
+       if (!next || !date)
+               die("missing or bad author/committer line %s", line);
+       next++; date += 2;
+
+       *buf = next;
+       *sp = size - (next - line);
+
+       len = date - line;
+       memcpy(dst, line, len);
+       dst += len;
+
+       /* Is it already in new format? */
+       if (isdigit(*date)) {
+               int datelen = next - date;
+               memcpy(dst, date, datelen);
+               return len + datelen;
+       }
+
+       /*
+        * Hacky hacky: one of the sparse old-style commits does not have
+        * any date at all, but we can fake it by using the committer date.
+        */
+       if (*date == '\n' && strchr(next, '>'))
+               date = strchr(next, '>')+2;
+
+       return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
+}
+
+static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       char *new = xmalloc(size + 100);
+       unsigned long newlen = 0;
+
+       /* "tree <sha1>\n" */
+       memcpy(new + newlen, buffer, 46);
+       newlen += 46;
+       buffer = (char *) buffer + 46;
+       size -= 46;
+
+       /* "parent <sha1>\n" */
+       while (!memcmp(buffer, "parent ", 7)) {
+               memcpy(new + newlen, buffer, 48);
+               newlen += 48;
+               buffer = (char *) buffer + 48;
+               size -= 48;
+       }
+
+       /* "author xyz <xyz> date" */
+       newlen += convert_date_line(new + newlen, &buffer, &size);
+       /* "committer xyz <xyz> date" */
+       newlen += convert_date_line(new + newlen, &buffer, &size);
+
+       /* Rest */
+       memcpy(new + newlen, buffer, size);
+       newlen += size;
+
+       write_sha1_file(new, newlen, commit_type, result_sha1);
+       free(new);
+}
+
+static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
+{
+       void *orig_buffer = buffer;
+       unsigned long orig_size = size;
+
+       if (memcmp(buffer, "tree ", 5))
+               die("Bad commit '%s'", (char*) buffer);
+       convert_ascii_sha1((char *) buffer + 5);
+       buffer = (char *) buffer + 46;    /* "tree " + "hex sha1" + "\n" */
+       while (!memcmp(buffer, "parent ", 7)) {
+               convert_ascii_sha1((char *) buffer + 7);
+               buffer = (char *) buffer + 48;
+       }
+       convert_date(orig_buffer, orig_size, result_sha1);
+}
+
+static struct entry * convert_entry(unsigned char *sha1)
+{
+       struct entry *entry = lookup_entry(sha1);
+       enum object_type type;
+       void *buffer, *data;
+       unsigned long size;
+
+       if (entry->converted)
+               return entry;
+       data = read_sha1_file(sha1, &type, &size);
+       if (!data)
+               die("unable to read object %s", sha1_to_hex(sha1));
+
+       buffer = xmalloc(size);
+       memcpy(buffer, data, size);
+
+       if (type == OBJ_BLOB) {
+               write_sha1_file(buffer, size, blob_type, entry->new_sha1);
+       } else if (type == OBJ_TREE)
+               convert_tree(buffer, size, entry->new_sha1);
+       else if (type == OBJ_COMMIT)
+               convert_commit(buffer, size, entry->new_sha1);
+       else
+               die("unknown object type %d in %s", type, sha1_to_hex(sha1));
+       entry->converted = 1;
+       free(buffer);
+       free(data);
+       return entry;
+}
+
+int main(int argc, char **argv)
+{
+       unsigned char sha1[20];
+       struct entry *entry;
+
+       setup_git_directory();
+
+       if (argc != 2)
+               usage("git-convert-objects <sha1>");
+       if (get_sha1(argv[1], sha1))
+               die("Not a valid object name %s", argv[1]);
+
+       entry = convert_entry(sha1);
+       printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
+       return 0;
+}
diff --git a/contrib/convert-objects/git-convert-objects.txt b/contrib/convert-objects/git-convert-objects.txt
new file mode 100644 (file)
index 0000000..9718abf
--- /dev/null
@@ -0,0 +1,28 @@
+git-convert-objects(1)
+======================
+
+NAME
+----
+git-convert-objects - Converts old-style git repository
+
+
+SYNOPSIS
+--------
+'git-convert-objects'
+
+DESCRIPTION
+-----------
+Converts old-style git repository to the latest format
+
+
+Author
+------
+Written by Linus Torvalds <torvalds@osdl.org>
+
+Documentation
+--------------
+Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
index 280557ecd4b0065ecc6c26cd8fda77906655fe75..2d77fd47ece21af7b79b3e5806521e208d5d92b0 100644 (file)
@@ -97,6 +97,21 @@ if there is already one that displays the same directory."
   :group 'git
   :type 'string)
 
+(defcustom git-show-uptodate nil
+  "Whether to display up-to-date files."
+  :group 'git
+  :type 'boolean)
+
+(defcustom git-show-ignored nil
+  "Whether to display ignored files."
+  :group 'git
+  :type 'boolean)
+
+(defcustom git-show-unknown t
+  "Whether to display unknown files."
+  :group 'git
+  :type 'boolean)
+
 
 (defface git-status-face
   '((((class color) (background light)) (:foreground "purple"))
@@ -479,6 +494,27 @@ and returns the process output as a string."
       (setf (git-fileinfo->orig-name info) nil)
       (setf (git-fileinfo->needs-refresh info) t))))
 
+(defun git-set-filenames-state (status files state)
+  "Set the state of a list of named files."
+  (when files
+    (setq files (sort files #'string-lessp))
+    (let ((file (pop files))
+          (node (ewoc-nth status 0)))
+      (while (and file node)
+        (let ((info (ewoc-data node)))
+          (cond ((string-lessp (git-fileinfo->name info) file)
+                 (setq node (ewoc-next status node)))
+                ((string-equal (git-fileinfo->name info) file)
+                 (unless (eq (git-fileinfo->state info) state)
+                   (setf (git-fileinfo->state info) state)
+                   (setf (git-fileinfo->rename-state info) nil)
+                   (setf (git-fileinfo->orig-name info) nil)
+                   (setf (git-fileinfo->needs-refresh info) t))
+                 (setq file (pop files)))
+                (t (setq file (pop files)))))))
+    (unless state  ;; delete files whose state has been set to nil
+      (ewoc-filter status (lambda (info) (git-fileinfo->state info))))))
+
 (defun git-state-code (code)
   "Convert from a string to a added/deleted/modified state."
   (case (string-to-char code)
@@ -532,19 +568,36 @@ and returns the process output as a string."
                   "  " (git-escape-file-name (git-fileinfo->name info))
                   (git-rename-as-string info))))
 
-(defun git-insert-fileinfo (status info &optional refresh)
-  "Insert INFO in the status buffer, optionally refreshing an existing one."
-  (let ((node (and refresh
-                   (git-find-status-file status (git-fileinfo->name info)))))
-    (setf (git-fileinfo->needs-refresh info) t)
-    (when node   ;preserve the marked flag
-      (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node))))
-    (if node (setf (ewoc-data node) info) (ewoc-enter-last status info))))
+(defun git-insert-info-list (status infolist)
+  "Insert a list of file infos in the status buffer, replacing existing ones if any."
+  (setq infolist (sort infolist
+                       (lambda (info1 info2)
+                         (string-lessp (git-fileinfo->name info1)
+                                       (git-fileinfo->name info2)))))
+  (let ((info (pop infolist))
+        (node (ewoc-nth status 0)))
+    (while info
+      (setf (git-fileinfo->needs-refresh info) t)
+      (cond ((not node)
+             (ewoc-enter-last status info)
+             (setq info (pop infolist)))
+            ((string-lessp (git-fileinfo->name (ewoc-data node))
+                           (git-fileinfo->name info))
+             (setq node (ewoc-next status node)))
+            ((string-equal (git-fileinfo->name (ewoc-data node))
+                           (git-fileinfo->name info))
+              ;; preserve the marked flag
+              (setf (git-fileinfo->marked info) (git-fileinfo->marked (ewoc-data node)))
+              (setf (ewoc-data node) info)
+              (setq info (pop infolist)))
+            (t
+             (ewoc-enter-before status node info)
+             (setq info (pop infolist)))))))
 
 (defun git-run-diff-index (status files)
   "Run git-diff-index on FILES and parse the results into STATUS.
 Return the list of files that haven't been handled."
-  (let ((refresh files))
+  (let (infolist)
     (with-temp-buffer
       (apply #'git-run-command t nil "diff-index" "-z" "-M" "HEAD" "--" files)
       (goto-char (point-min))
@@ -558,13 +611,14 @@ Return the list of files that haven't been handled."
               (new-name (match-string 8)))
           (if new-name  ; copy or rename
               (if (eq ?C (string-to-char state))
-                  (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) refresh)
-                (git-insert-fileinfo status (git-create-fileinfo 'deleted name 0 0 'rename new-name) refresh)
-                (git-insert-fileinfo status (git-create-fileinfo 'added new-name old-perm new-perm 'rename name)) refresh)
-            (git-insert-fileinfo status (git-create-fileinfo (git-state-code state) name old-perm new-perm) refresh))
+                  (push (git-create-fileinfo 'added new-name old-perm new-perm 'copy name) infolist)
+                (push (git-create-fileinfo 'deleted name 0 0 'rename new-name) infolist)
+                (push (git-create-fileinfo 'added new-name old-perm new-perm 'rename name) infolist))
+            (push (git-create-fileinfo (git-state-code state) name old-perm new-perm) infolist))
           (setq files (delete name files))
-          (when new-name (setq files (delete new-name files)))))))
-  files)
+          (when new-name (setq files (delete new-name files))))))
+    (git-insert-info-list status infolist)
+    files))
 
 (defun git-find-status-file (status file)
   "Find a given file in the status ewoc and return its node."
@@ -576,16 +630,16 @@ Return the list of files that haven't been handled."
 (defun git-run-ls-files (status files default-state &rest options)
   "Run git-ls-files on FILES and parse the results into STATUS.
 Return the list of files that haven't been handled."
-  (let ((refresh files))
+  (let (infolist)
     (with-temp-buffer
-      (apply #'git-run-command t nil "ls-files" "-z" "-t" (append options (list "--") files))
+      (apply #'git-run-command t nil "ls-files" "-z" (append options (list "--") files))
       (goto-char (point-min))
-      (while (re-search-forward "\\([HMRCK?]\\) \\([^\0]*\\)\0" nil t 1)
-        (let ((state (match-string 1))
-              (name (match-string 2)))
-          (git-insert-fileinfo status (git-create-fileinfo (or (git-state-code state) default-state) name) refresh)
-          (setq files (delete name files))))))
-  files)
+      (while (re-search-forward "\\([^\0]*\\)\0" nil t 1)
+        (let ((name (match-string 1)))
+          (push (git-create-fileinfo default-state name) infolist)
+          (setq files (delete name files)))))
+    (git-insert-info-list status infolist)
+    files))
 
 (defun git-run-ls-unmerged (status files)
   "Run git-ls-files -u on FILES and parse the results into STATUS."
@@ -594,9 +648,8 @@ Return the list of files that haven't been handled."
     (goto-char (point-min))
     (let (unmerged-files)
       (while (re-search-forward "[0-7]\\{6\\} [0-9a-f]\\{40\\} [123]\t\\([^\0]+\\)\0" nil t)
-        (let ((node (git-find-status-file status (match-string 1))))
-          (when node (push (ewoc-data node) unmerged-files))))
-      (git-set-files-state unmerged-files 'unmerged))))
+        (push (match-string 1) unmerged-files))
+      (git-set-filenames-state status unmerged-files 'unmerged))))
 
 (defun git-get-exclude-files ()
   "Get the list of exclude files to pass to git-ls-files."
@@ -608,34 +661,30 @@ Return the list of files that haven't been handled."
       (push config files))
     files))
 
+(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
+           (concat "--exclude-per-directory=" git-per-dir-ignore-file)
+           (append options (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
+
 (defun git-update-status-files (files &optional default-state)
   "Update the status of FILES from the index."
   (unless git-status (error "Not in git-status buffer."))
-  (let* ((status git-status)
-         (remaining-files
+  (unless files
+    (when git-show-uptodate (git-run-ls-files git-status nil 'uptodate "-c")))
+  (let* ((remaining-files
           (if (git-empty-db-p) ; we need some special handling for an empty db
-              (git-run-ls-files status files 'added "-c")
-            (git-run-diff-index status files))))
-    (git-run-ls-unmerged status files)
-    (when (or (not files) remaining-files)
-      (let ((exclude-files (git-get-exclude-files)))
-        (setq remaining-files (apply #'git-run-ls-files status remaining-files 'unknown "-o"
-                                     (concat "--exclude-per-directory=" git-per-dir-ignore-file)
-                                     (mapcar (lambda (f) (concat "--exclude-from=" f)) exclude-files)))))
-    ; mark remaining files with the default state (or remove them if nil)
-    (when remaining-files
-      (if default-state
-          (ewoc-map (lambda (info)
-                      (when (member (git-fileinfo->name info) remaining-files)
-                        (git-set-files-state (list info) default-state))
-                      nil)
-                    status)
-        (ewoc-filter status
-                     (lambda (info files)
-                       (not (member (git-fileinfo->name info) files)))
-                     remaining-files)))
+              (git-run-ls-files git-status files 'added "-c")
+            (git-run-diff-index git-status files))))
+    (git-run-ls-unmerged git-status files)
+    (when (or remaining-files (and git-show-unknown (not files)))
+      (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'unknown "-o")))
+    (when (or remaining-files (and git-show-ignored (not files)))
+      (setq remaining-files (git-run-ls-files-with-excludes git-status remaining-files 'ignored "-o" "-i")))
+    (git-set-filenames-state git-status remaining-files default-state)
     (git-refresh-files)
-    (git-refresh-ewoc-hf status)))
+    (git-refresh-ewoc-hf git-status)))
 
 (defun git-marked-files ()
   "Return a list of all marked files, or if none a list containing just the file at cursor position."
@@ -853,7 +902,7 @@ Return the list of files that haven't been handled."
 (defun git-add-file ()
   "Add marked file(s) to the index cache."
   (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'unknown))))
+  (let ((files (git-get-filenames (git-marked-files-state 'unknown 'ignored))))
     (unless files
       (push (file-relative-name (read-file-name "File to add: " nil nil t)) files))
     (apply #'git-run-command nil nil "update-index" "--add" "--" files)
@@ -871,7 +920,7 @@ Return the list of files that haven't been handled."
 (defun git-remove-file ()
   "Remove the marked file(s)."
   (interactive)
-  (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate))))
+  (let ((files (git-get-filenames (git-marked-files-state 'added 'modified 'unknown 'uptodate 'ignored))))
     (unless files
       (push (file-relative-name (read-file-name "File to remove: " nil nil t)) files))
     (if (yes-or-no-p
@@ -916,11 +965,41 @@ Return the list of files that haven't been handled."
   (interactive)
   (ewoc-filter git-status
                (lambda (info)
-                 (not (or (eq (git-fileinfo->state info) 'ignored)
-                          (eq (git-fileinfo->state info) 'uptodate)))))
+                 (case (git-fileinfo->state info)
+                   ('ignored git-show-ignored)
+                   ('uptodate git-show-uptodate)
+                   ('unknown git-show-unknown)
+                   (t t))))
   (unless (ewoc-nth git-status 0)  ; refresh header if list is empty
     (git-refresh-ewoc-hf git-status)))
 
+(defun git-toggle-show-uptodate ()
+  "Toogle the option for showing up-to-date files."
+  (interactive)
+  (if (setq git-show-uptodate (not git-show-uptodate))
+      (git-refresh-status)
+    (git-remove-handled)))
+
+(defun git-toggle-show-ignored ()
+  "Toogle the option for showing ignored files."
+  (interactive)
+  (if (setq git-show-ignored (not git-show-ignored))
+      (progn
+        (git-run-ls-files-with-excludes git-status nil 'ignored "-o" "-i")
+        (git-refresh-files)
+        (git-refresh-ewoc-hf git-status))
+    (git-remove-handled)))
+
+(defun git-toggle-show-unknown ()
+  "Toogle the option for showing unknown files."
+  (interactive)
+  (if (setq git-show-unknown (not git-show-unknown))
+      (progn
+        (git-run-ls-files-with-excludes git-status nil 'unknown "-o")
+        (git-refresh-files)
+        (git-refresh-ewoc-hf git-status))
+    (git-remove-handled)))
+
 (defun git-setup-diff-buffer (buffer)
   "Setup a buffer for displaying a diff."
   (let ((dir default-directory))
@@ -1146,7 +1225,8 @@ Return the list of files that haven't been handled."
 
 (unless git-status-mode-map
   (let ((map (make-keymap))
-        (diff-map (make-sparse-keymap)))
+        (diff-map (make-sparse-keymap))
+        (toggle-map (make-sparse-keymap)))
     (suppress-keymap map)
     (define-key map "?"   'git-help)
     (define-key map "h"   'git-help)
@@ -1170,6 +1250,7 @@ Return the list of files that haven't been handled."
     (define-key map "q"   'git-status-quit)
     (define-key map "r"   'git-remove-file)
     (define-key map "R"   'git-resolve-file)
+    (define-key map "t"    toggle-map)
     (define-key map "T"   'git-toggle-all-marks)
     (define-key map "u"   'git-unmark-file)
     (define-key map "U"   'git-revert-file)
@@ -1186,6 +1267,11 @@ Return the list of files that haven't been handled."
     (define-key diff-map "h" 'git-diff-file-merge-head)
     (define-key diff-map "m" 'git-diff-file-mine)
     (define-key diff-map "o" 'git-diff-file-other)
+    ; the toggle submap
+    (define-key toggle-map "u" 'git-toggle-show-uptodate)
+    (define-key toggle-map "i" 'git-toggle-show-ignored)
+    (define-key toggle-map "k" 'git-toggle-show-unknown)
+    (define-key toggle-map "m" 'git-toggle-all-marks)
     (setq git-status-mode-map map)))
 
 ;; git mode should only run in the *git status* buffer
@@ -1207,6 +1293,9 @@ Commands:
   (let ((status (ewoc-create 'git-fileinfo-prettyprint "" "")))
     (set (make-local-variable 'git-status) status))
   (set (make-local-variable 'list-buffers-directory) default-directory)
+  (make-local-variable 'git-show-uptodate)
+  (make-local-variable 'git-show-ignored)
+  (make-local-variable 'git-show-unknown)
   (run-hooks 'git-status-mode-hook)))
 
 (defun git-find-status-buffer (dir)
diff --git a/contrib/examples/git-reset.sh b/contrib/examples/git-reset.sh
new file mode 100755 (executable)
index 0000000..bafeb52
--- /dev/null
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
+#
+USAGE='[--mixed | --soft | --hard]  [<commit-ish>] [ [--] <paths>...]'
+SUBDIRECTORY_OK=Yes
+. git-sh-setup
+set_reflog_action "reset $*"
+require_work_tree
+
+update= reset_type=--mixed
+unset rev
+
+while test $# != 0
+do
+       case "$1" in
+       --mixed | --soft | --hard)
+               reset_type="$1"
+               ;;
+       --)
+               break
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               rev=$(git rev-parse --verify "$1") || exit
+               shift
+               break
+               ;;
+       esac
+       shift
+done
+
+: ${rev=HEAD}
+rev=$(git rev-parse --verify $rev^0) || exit
+
+# Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
+case "$1" in --) shift ;; esac
+
+# git reset --mixed tree [--] paths... can be used to
+# load chosen paths from the tree into the index without
+# affecting the working tree nor HEAD.
+if test $# != 0
+then
+       test "$reset_type" = "--mixed" ||
+               die "Cannot do partial $reset_type reset."
+
+       git diff-index --cached $rev -- "$@" |
+       sed -e 's/^:\([0-7][0-7]*\) [0-7][0-7]* \([0-9a-f][0-9a-f]*\) [0-9a-f][0-9a-f]* [A-Z]   \(.*\)$/\1 \2   \3/' |
+       git update-index --add --remove --index-info || exit
+       git update-index --refresh
+       exit
+fi
+
+cd_to_toplevel
+
+if test "$reset_type" = "--hard"
+then
+       update=-u
+fi
+
+# Soft reset does not touch the index file nor the working tree
+# at all, but requires them in a good order.  Other resets reset
+# the index file to the tree object we are switching to.
+if test "$reset_type" = "--soft"
+then
+       if test -f "$GIT_DIR/MERGE_HEAD" ||
+          test "" != "$(git ls-files --unmerged)"
+       then
+               die "Cannot do a soft reset in the middle of a merge."
+       fi
+else
+       git read-tree -v --reset $update "$rev" || exit
+fi
+
+# Any resets update HEAD to the head being switched to.
+if orig=$(git rev-parse --verify HEAD 2>/dev/null)
+then
+       echo "$orig" >"$GIT_DIR/ORIG_HEAD"
+else
+       rm -f "$GIT_DIR/ORIG_HEAD"
+fi
+git update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
+update_ref_status=$?
+
+case "$reset_type" in
+--hard )
+       test $update_ref_status = 0 && {
+               printf "HEAD is now at "
+               GIT_PAGER= git log --max-count=1 --pretty=oneline \
+                       --abbrev-commit HEAD
+       }
+       ;;
+--soft )
+       ;; # Nothing else to do
+--mixed )
+       # Report what has not been updated.
+       git update-index --refresh
+       ;;
+esac
+
+rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" \
+       "$GIT_DIR/SQUASH_MSG" "$GIT_DIR/MERGE_MSG"
+
+exit $update_ref_status
diff --git a/contrib/fast-import/git-import.perl b/contrib/fast-import/git-import.perl
new file mode 100755 (executable)
index 0000000..f9fef6d
--- /dev/null
@@ -0,0 +1,64 @@
+#!/usr/bin/perl
+#
+# Performs an initial import of a directory. This is the equivalent
+# of doing 'git init; git add .; git commit'. It's a little slower,
+# but is meant to be a simple fast-import example.
+
+use strict;
+use File::Find;
+
+my $USAGE = 'Usage: git-import branch import-message';
+my $branch = shift or die "$USAGE\n";
+my $message = shift or die "$USAGE\n";
+
+chomp(my $username = `git config user.name`);
+chomp(my $email = `git config user.email`);
+die 'You need to set user name and email'
+  unless $username && $email;
+
+system('git init');
+open(my $fi, '|-', qw(git fast-import --date-format=now))
+  or die "unable to spawn fast-import: $!";
+
+print $fi <<EOF;
+commit refs/heads/$branch
+committer $username <$email> now
+data <<MSGEOF
+$message
+MSGEOF
+
+EOF
+
+find(
+  sub {
+    if($File::Find::name eq './.git') {
+      $File::Find::prune = 1;
+      return;
+    }
+    return unless -f $_;
+
+    my $fn = $File::Find::name;
+    $fn =~ s#^.\/##;
+
+    open(my $in, '<', $_)
+      or die "unable to open $fn: $!";
+    my @st = stat($in)
+      or die "unable to stat $fn: $!";
+    my $len = $st[7];
+
+    print $fi "M 644 inline $fn\n";
+    print $fi "data $len\n";
+    while($len > 0) {
+      my $r = read($in, my $buf, $len < 4096 ? $len : 4096);
+      defined($r) or die "read error from $fn: $!";
+      $r > 0 or die "premature EOF from $fn: $!";
+      print $fi $buf;
+      $len -= $r;
+    }
+    print $fi "\n";
+
+  }, '.'
+);
+
+close($fi);
+exit $?;
diff --git a/contrib/fast-import/git-import.sh b/contrib/fast-import/git-import.sh
new file mode 100755 (executable)
index 0000000..0ca7718
--- /dev/null
@@ -0,0 +1,38 @@
+#!/bin/sh
+#
+# Performs an initial import of a directory. This is the equivalent
+# of doing 'git init; git add .; git commit'. It's a lot slower,
+# but is meant to be a simple fast-import example.
+
+if [ -z "$1" -o -z "$2" ]; then
+       echo "Usage: git-import branch import-message"
+       exit 1
+fi
+
+USERNAME="$(git config user.name)"
+EMAIL="$(git config user.email)"
+
+if [ -z "$USERNAME" -o -z "$EMAIL" ]; then
+       echo "You need to set user name and email"
+       exit 1
+fi
+
+git init
+
+(
+       cat <<EOF
+commit refs/heads/$1
+committer $USERNAME <$EMAIL> now
+data <<MSGEOF
+$2
+MSGEOF
+
+EOF
+       find * -type f|while read i;do
+               echo "M 100644 inline $i"
+               echo data $(stat -c '%s' "$i")
+               cat "$i"
+               echo
+       done
+       echo
+) | git fast-import --date-format=now
index 65c57ac4d8247ec279e4f018d36ef93dcca78061..557649a14ad0e3a7ab4d0f1b9b128d1ba64d0757 100755 (executable)
@@ -289,6 +289,19 @@ def createOrUpdateBranchesFromOrigin(localRefPrefix = "refs/remotes/p4/", silent
 def originP4BranchesExist():
         return gitBranchExists("origin") or gitBranchExists("origin/p4") or gitBranchExists("origin/p4/master")
 
+def p4ChangesForPaths(depotPaths, changeRange):
+    assert depotPaths
+    output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, changeRange)
+                                                        for p in depotPaths]))
+
+    changes = []
+    for line in output:
+        changeNum = line.split(" ")[1]
+        changes.append(int(changeNum))
+
+    changes.sort()
+    return changes
+
 class Command:
     def __init__(self):
         self.usage = "usage: %prog [options]"
@@ -672,9 +685,8 @@ class P4Submit(Command):
             f.close();
 
         os.chdir(self.clientPath)
-        response = raw_input("Do you want to sync %s with p4 sync? [y]es/[n]o " % self.clientPath)
-        if response == "y" or response == "yes":
-            system("p4 sync ...")
+        print "Syncronizing p4 checkout..."
+        system("p4 sync ...")
 
         if self.reset:
             self.firstTime = True
@@ -713,10 +725,14 @@ class P4Submit(Command):
             else:
                 print "All changes applied!"
                 os.chdir(self.oldWorkingDirectory)
-                response = raw_input("Do you want to sync from Perforce now using git-p4 rebase? [y]es/[n]o ")
+
+                sync = P4Sync()
+                sync.run([])
+
+                response = raw_input("Do you want to rebase current HEAD from Perforce now using git-p4 rebase? [y]es/[n]o ")
                 if response == "y" or response == "yes":
                     rebase = P4Rebase()
-                    rebase.run([])
+                    rebase.rebase()
             os.remove(self.configFile)
 
         return True
@@ -1110,6 +1126,186 @@ class P4Sync(Command):
         self.keepRepoPath = (d.has_key('options')
                              and ('keepRepoPath' in d['options']))
 
+    def gitRefForBranch(self, branch):
+        if branch == "main":
+            return self.refPrefix + "master"
+
+        if len(branch) <= 0:
+            return branch
+
+        return self.refPrefix + self.projectName + branch
+
+    def gitCommitByP4Change(self, ref, change):
+        if self.verbose:
+            print "looking in ref " + ref + " for change %s using bisect..." % change
+
+        earliestCommit = ""
+        latestCommit = parseRevision(ref)
+
+        while True:
+            if self.verbose:
+                print "trying: earliest %s latest %s" % (earliestCommit, latestCommit)
+            next = read_pipe("git rev-list --bisect %s %s" % (latestCommit, earliestCommit)).strip()
+            if len(next) == 0:
+                if self.verbose:
+                    print "argh"
+                return ""
+            log = extractLogMessageFromGitCommit(next)
+            settings = extractSettingsGitLog(log)
+            currentChange = int(settings['change'])
+            if self.verbose:
+                print "current change %s" % currentChange
+
+            if currentChange == change:
+                if self.verbose:
+                    print "found %s" % next
+                return next
+
+            if currentChange < change:
+                earliestCommit = "^%s" % next
+            else:
+                latestCommit = "%s" % next
+
+        return ""
+
+    def importNewBranch(self, branch, maxChange):
+        # make fast-import flush all changes to disk and update the refs using the checkpoint
+        # command so that we can try to find the branch parent in the git history
+        self.gitStream.write("checkpoint\n\n");
+        self.gitStream.flush();
+        branchPrefix = self.depotPaths[0] + branch + "/"
+        range = "@1,%s" % maxChange
+        #print "prefix" + branchPrefix
+        changes = p4ChangesForPaths([branchPrefix], range)
+        if len(changes) <= 0:
+            return False
+        firstChange = changes[0]
+        #print "first change in branch: %s" % firstChange
+        sourceBranch = self.knownBranches[branch]
+        sourceDepotPath = self.depotPaths[0] + sourceBranch
+        sourceRef = self.gitRefForBranch(sourceBranch)
+        #print "source " + sourceBranch
+
+        branchParentChange = int(p4Cmd("changes -m 1 %s...@1,%s" % (sourceDepotPath, firstChange))["change"])
+        #print "branch parent: %s" % branchParentChange
+        gitParent = self.gitCommitByP4Change(sourceRef, branchParentChange)
+        if len(gitParent) > 0:
+            self.initialParents[self.gitRefForBranch(branch)] = gitParent
+            #print "parent git commit: %s" % gitParent
+
+        self.importChanges(changes)
+        return True
+
+    def importChanges(self, changes):
+        cnt = 1
+        for change in changes:
+            description = p4Cmd("describe %s" % change)
+            self.updateOptionDict(description)
+
+            if not self.silent:
+                sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
+                sys.stdout.flush()
+            cnt = cnt + 1
+
+            try:
+                if self.detectBranches:
+                    branches = self.splitFilesIntoBranches(description)
+                    for branch in branches.keys():
+                        ## HACK  --hwn
+                        branchPrefix = self.depotPaths[0] + branch + "/"
+
+                        parent = ""
+
+                        filesForCommit = branches[branch]
+
+                        if self.verbose:
+                            print "branch is %s" % branch
+
+                        self.updatedBranches.add(branch)
+
+                        if branch not in self.createdBranches:
+                            self.createdBranches.add(branch)
+                            parent = self.knownBranches[branch]
+                            if parent == branch:
+                                parent = ""
+                            else:
+                                fullBranch = self.projectName + branch
+                                if fullBranch not in self.p4BranchesInGit:
+                                    if not self.silent:
+                                        print("\n    Importing new branch %s" % fullBranch);
+                                    if self.importNewBranch(branch, change - 1):
+                                        parent = ""
+                                        self.p4BranchesInGit.append(fullBranch)
+                                    if not self.silent:
+                                        print("\n    Resuming with change %s" % change);
+
+                                if self.verbose:
+                                    print "parent determined through known branches: %s" % parent
+
+                        branch = self.gitRefForBranch(branch)
+                        parent = self.gitRefForBranch(parent)
+
+                        if self.verbose:
+                            print "looking for initial parent for %s; current parent is %s" % (branch, parent)
+
+                        if len(parent) == 0 and branch in self.initialParents:
+                            parent = self.initialParents[branch]
+                            del self.initialParents[branch]
+
+                        self.commit(description, filesForCommit, branch, [branchPrefix], parent)
+                else:
+                    files = self.extractFilesFromCommit(description)
+                    self.commit(description, files, self.branch, self.depotPaths,
+                                self.initialParent)
+                    self.initialParent = ""
+            except IOError:
+                print self.gitError.read()
+                sys.exit(1)
+
+    def importHeadRevision(self, revision):
+        print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
+
+        details = { "user" : "git perforce import user", "time" : int(time.time()) }
+        details["desc"] = ("Initial import of %s from the state at revision %s"
+                           % (' '.join(self.depotPaths), revision))
+        details["change"] = revision
+        newestRevision = 0
+
+        fileCnt = 0
+        for info in p4CmdList("files "
+                              +  ' '.join(["%s...%s"
+                                           % (p, revision)
+                                           for p in self.depotPaths])):
+
+            if info['code'] == 'error':
+                sys.stderr.write("p4 returned an error: %s\n"
+                                 % info['data'])
+                sys.exit(1)
+
+
+            change = int(info["change"])
+            if change > newestRevision:
+                newestRevision = change
+
+            if info["action"] == "delete":
+                # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
+                #fileCnt = fileCnt + 1
+                continue
+
+            for prop in ["depotFile", "rev", "action", "type" ]:
+                details["%s%s" % (prop, fileCnt)] = info[prop]
+
+            fileCnt = fileCnt + 1
+
+        details["change"] = newestRevision
+        self.updateOptionDict(details)
+        try:
+            self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
+        except IOError:
+            print "IO error with git fast-import. Is your git version recent enough?"
+            print self.gitError.read()
+
+
     def run(self, args):
         self.depotPaths = []
         self.changeRange = ""
@@ -1207,7 +1403,7 @@ class P4Sync(Command):
 
             self.depotPaths = sorted(args)
 
-        self.revision = ""
+        revision = ""
         self.users = {}
 
         newPaths = []
@@ -1218,15 +1414,15 @@ class P4Sync(Command):
                 if self.changeRange == "@all":
                     self.changeRange = ""
                 elif ',' not in self.changeRange:
-                    self.revision = self.changeRange
+                    revision = self.changeRange
                     self.changeRange = ""
                 p = p[:atIdx]
             elif p.find("#") != -1:
                 hashIdx = p.index("#")
-                self.revision = p[hashIdx:]
+                revision = p[hashIdx:]
                 p = p[:hashIdx]
             elif self.previousDepotPaths == []:
-                self.revision = "#head"
+                revision = "#head"
 
             p = re.sub ("\.\.\.$", "", p)
             if not p.endswith("/"):
@@ -1267,49 +1463,8 @@ class P4Sync(Command):
         self.gitStream = importProcess.stdin
         self.gitError = importProcess.stderr
 
-        if self.revision:
-            print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), self.revision, self.branch)
-
-            details = { "user" : "git perforce import user", "time" : int(time.time()) }
-            details["desc"] = ("Initial import of %s from the state at revision %s"
-                               % (' '.join(self.depotPaths), self.revision))
-            details["change"] = self.revision
-            newestRevision = 0
-
-            fileCnt = 0
-            for info in p4CmdList("files "
-                                  +  ' '.join(["%s...%s"
-                                               % (p, self.revision)
-                                               for p in self.depotPaths])):
-
-                if info['code'] == 'error':
-                    sys.stderr.write("p4 returned an error: %s\n"
-                                     % info['data'])
-                    sys.exit(1)
-
-
-                change = int(info["change"])
-                if change > newestRevision:
-                    newestRevision = change
-
-                if info["action"] == "delete":
-                    # don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
-                    #fileCnt = fileCnt + 1
-                    continue
-
-                for prop in ["depotFile", "rev", "action", "type" ]:
-                    details["%s%s" % (prop, fileCnt)] = info[prop]
-
-                fileCnt = fileCnt + 1
-
-            details["change"] = newestRevision
-            self.updateOptionDict(details)
-            try:
-                self.commit(details, self.extractFilesFromCommit(details), self.branch, self.depotPaths)
-            except IOError:
-                print "IO error with git fast-import. Is your git version recent enough?"
-                print self.gitError.read()
-
+        if revision:
+            self.importHeadRevision(revision)
         else:
             changes = []
 
@@ -1327,15 +1482,7 @@ class P4Sync(Command):
                 if self.verbose:
                     print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
                                                               self.changeRange)
-                assert self.depotPaths
-                output = read_pipe_lines("p4 changes " + ' '.join (["%s...%s" % (p, self.changeRange)
-                                                                    for p in self.depotPaths]))
-
-                for line in output:
-                    changeNum = line.split(" ")[1]
-                    changes.append(int(changeNum))
-
-                changes.sort()
+                changes = p4ChangesForPaths(self.depotPaths, self.changeRange)
 
                 if len(self.maxChanges) > 0:
                     changes = changes[:min(int(self.maxChanges), len(changes))]
@@ -1350,74 +1497,7 @@ class P4Sync(Command):
 
             self.updatedBranches = set()
 
-            cnt = 1
-            for change in changes:
-                description = p4Cmd("describe %s" % change)
-                self.updateOptionDict(description)
-
-                if not self.silent:
-                    sys.stdout.write("\rImporting revision %s (%s%%)" % (change, cnt * 100 / len(changes)))
-                    sys.stdout.flush()
-                cnt = cnt + 1
-
-                try:
-                    if self.detectBranches:
-                        branches = self.splitFilesIntoBranches(description)
-                        for branch in branches.keys():
-                            ## HACK  --hwn
-                            branchPrefix = self.depotPaths[0] + branch + "/"
-
-                            parent = ""
-
-                            filesForCommit = branches[branch]
-
-                            if self.verbose:
-                                print "branch is %s" % branch
-
-                            self.updatedBranches.add(branch)
-
-                            if branch not in self.createdBranches:
-                                self.createdBranches.add(branch)
-                                parent = self.knownBranches[branch]
-                                if parent == branch:
-                                    parent = ""
-                                elif self.verbose:
-                                    print "parent determined through known branches: %s" % parent
-
-                            # main branch? use master
-                            if branch == "main":
-                                branch = "master"
-                            else:
-
-                                ## FIXME
-                                branch = self.projectName + branch
-
-                            if parent == "main":
-                                parent = "master"
-                            elif len(parent) > 0:
-                                ## FIXME
-                                parent = self.projectName + parent
-
-                            branch = self.refPrefix + branch
-                            if len(parent) > 0:
-                                parent = self.refPrefix + parent
-
-                            if self.verbose:
-                                print "looking for initial parent for %s; current parent is %s" % (branch, parent)
-
-                            if len(parent) == 0 and branch in self.initialParents:
-                                parent = self.initialParents[branch]
-                                del self.initialParents[branch]
-
-                            self.commit(description, filesForCommit, branch, [branchPrefix], parent)
-                    else:
-                        files = self.extractFilesFromCommit(description)
-                        self.commit(description, files, self.branch, self.depotPaths,
-                                    self.initialParent)
-                        self.initialParent = ""
-                except IOError:
-                    print self.gitError.read()
-                    sys.exit(1)
+            self.importChanges(changes)
 
             if not self.silent:
                 print ""
@@ -1427,7 +1507,6 @@ class P4Sync(Command):
                         sys.stdout.write("%s " % b)
                     sys.stdout.write("\n")
 
-
         self.gitStream.close()
         if importProcess.wait() != 0:
             die("fast-import failed: %s" % self.gitError.read())
@@ -1448,6 +1527,9 @@ class P4Rebase(Command):
         sync = P4Sync()
         sync.run([])
 
+        return self.rebase()
+
+    def rebase(self):
         [upstream, settings] = findUpstreamBranchPoint()
         if len(upstream) == 0:
             die("Cannot find upstream branchpoint for rebase")
index c589a39a0c81818150575c74866a57619e1adf2a..1f88099df4b2213c0dca58b22567bc1d91e4adc2 100644 (file)
@@ -571,6 +571,15 @@ generate_delete_general_email()
        echo $LOGEND
 }
 
+send_mail()
+{
+       if [ -n "$envelopesender" ]; then
+               /usr/sbin/sendmail -t -f "$envelopesender"
+       else
+               /usr/sbin/sendmail -t
+       fi
+}
+
 # ---------------------------- main()
 
 # --- Constants
@@ -607,13 +616,8 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
        # resend an email; they could redirect the output to sendmail themselves
        PAGER= generate_email $2 $3 $1
 else
-       if [ -n "$envelopesender" ]; then
-               envelopesender="-f '$envelopesender'"
-       fi
-
        while read oldrev newrev refname
        do
-               generate_email $oldrev $newrev $refname |
-               /usr/sbin/sendmail -t $envelopesender
+               generate_email $oldrev $newrev $refname | send_mail
        done
 fi
diff --git a/contrib/hooks/setgitperms.perl b/contrib/hooks/setgitperms.perl
new file mode 100644 (file)
index 0000000..5e3b89d
--- /dev/null
@@ -0,0 +1,213 @@
+#!/usr/bin/perl
+#
+# Copyright (c) 2006 Josh England
+#
+# This script can be used to save/restore full permissions and ownership data
+# within a git working tree.
+#
+# To save permissions/ownership data, place this script in your .git/hooks
+# directory and enable a `pre-commit` hook with the following lines:
+#      #!/bin/sh
+#     . git-sh-setup
+#     $GIT_DIR/hooks/setgitperms.perl -r
+#
+# To restore permissions/ownership data, place this script in your .git/hooks
+# directory and enable a `post-merge` hook with the following lines:
+#      #!/bin/sh
+#     . git-sh-setup
+#     $GIT_DIR/hooks/setgitperms.perl -w
+#
+use strict;
+use Getopt::Long;
+use File::Find;
+use File::Basename;
+
+my $usage =
+"Usage: setgitperms.perl [OPTION]... <--read|--write>
+This program uses a file `.gitmeta` to store/restore permissions and uid/gid
+info for all files/dirs tracked by git in the repository.
+
+---------------------------------Read Mode-------------------------------------
+-r,  --read         Reads perms/etc from working dir into a .gitmeta file
+-s,  --stdout       Output to stdout instead of .gitmeta
+-d,  --diff         Show unified diff of perms file (XOR with --stdout)
+
+---------------------------------Write Mode------------------------------------
+-w,  --write        Modify perms/etc in working dir to match the .gitmeta file
+-v,  --verbose      Be verbose
+
+\n";
+
+my ($stdout, $showdiff, $verbose, $read_mode, $write_mode);
+
+if ((@ARGV < 0) || !GetOptions(
+                              "stdout",         \$stdout,
+                              "diff",           \$showdiff,
+                              "read",           \$read_mode,
+                              "write",          \$write_mode,
+                              "verbose",        \$verbose,
+                             )) { die $usage; }
+die $usage unless ($read_mode xor $write_mode);
+
+my $topdir = `git-rev-parse --show-cdup` or die "\n"; chomp $topdir;
+my $gitdir = $topdir . '.git';
+my $gitmeta = $topdir . '.gitmeta';
+
+if ($write_mode) {
+    # Update the working dir permissions/ownership based on data from .gitmeta
+    open (IN, "<$gitmeta") or die "Could not open $gitmeta for reading: $!\n";
+    while (defined ($_ = <IN>)) {
+       chomp;
+       if (/^(.*)  mode=(\S+)\s+uid=(\d+)\s+gid=(\d+)/) {
+           # Compare recorded perms to actual perms in the working dir
+           my ($path, $mode, $uid, $gid) = ($1, $2, $3, $4);
+           my $fullpath = $topdir . $path;
+           my (undef,undef,$wmode,undef,$wuid,$wgid) = lstat($fullpath);
+           $wmode = sprintf "%04o", $wmode & 07777;
+           if ($mode ne $wmode) {
+               $verbose && print "Updating permissions on $path: old=$wmode, new=$mode\n";
+               chmod oct($mode), $fullpath;
+           }
+           if ($uid != $wuid || $gid != $wgid) {
+               if ($verbose) {
+                   # Print out user/group names instead of uid/gid
+                   my $pwname  = getpwuid($uid);
+                   my $grpname  = getgrgid($gid);
+                   my $wpwname  = getpwuid($wuid);
+                   my $wgrpname  = getgrgid($wgid);
+                   $pwname = $uid if !defined $pwname;
+                   $grpname = $gid if !defined $grpname;
+                   $wpwname = $wuid if !defined $wpwname;
+                   $wgrpname = $wgid if !defined $wgrpname;
+
+                   print "Updating uid/gid on $path: old=$wpwname/$wgrpname, new=$pwname/$grpname\n";
+               }
+               chown $uid, $gid, $fullpath;
+           }
+       }
+       else {
+           warn "Invalid input format in $gitmeta:\n\t$_\n";
+       }
+    }
+    close IN;
+}
+elsif ($read_mode) {
+    # Handle merge conflicts in the .gitperms file
+    if (-e "$gitdir/MERGE_MSG") {
+       if (`grep ====== $gitmeta`) {
+           # Conflict not resolved -- abort the commit
+           print "PERMISSIONS/OWNERSHIP CONFLICT\n";
+           print "    Resolve the conflict in the $gitmeta file and then run\n";
+           print "    `.git/hooks/setgitperms.perl --write` to reconcile.\n";
+           exit 1;
+       }
+       elsif (`grep $gitmeta $gitdir/MERGE_MSG`) {
+           # A conflict in .gitmeta has been manually resolved. Verify that
+           # the working dir perms matches the current .gitmeta perms for
+           # each file/dir that conflicted.
+           # This is here because a `setgitperms.perl --write` was not
+           # performed due to a merge conflict, so permissions/ownership
+           # may not be consistent with the manually merged .gitmeta file.
+           my @conflict_diff = `git show \$(cat $gitdir/MERGE_HEAD)`;
+           my @conflict_files;
+           my $metadiff = 0;
+
+           # Build a list of files that conflicted from the .gitmeta diff
+           foreach my $line (@conflict_diff) {
+               if ($line =~ m|^diff --git a/$gitmeta b/$gitmeta|) {
+                   $metadiff = 1;
+               }
+               elsif ($line =~ /^diff --git/) {
+                   $metadiff = 0;
+               }
+               elsif ($metadiff && $line =~ /^\+(.*)  mode=/) {
+                   push @conflict_files, $1;
+               }
+           }
+
+           # Verify that each conflict file now has permissions consistent
+           # with the .gitmeta file
+           foreach my $file (@conflict_files) {
+               my $absfile = $topdir . $file;
+               my $gm_entry = `grep "^$file  mode=" $gitmeta`;
+               if ($gm_entry =~ /mode=(\d+)  uid=(\d+)  gid=(\d+)/) {
+                   my ($gm_mode, $gm_uid, $gm_gid) = ($1, $2, $3);
+                   my (undef,undef,$mode,undef,$uid,$gid) = lstat("$absfile");
+                   $mode = sprintf("%04o", $mode & 07777);
+                   if (($gm_mode ne $mode) || ($gm_uid != $uid)
+                       || ($gm_gid != $gid)) {
+                       print "PERMISSIONS/OWNERSHIP CONFLICT\n";
+                       print "    Mismatch found for file: $file\n";
+                       print "    Run `.git/hooks/setgitperms.perl --write` to reconcile.\n";
+                       exit 1;
+                   }
+               }
+               else {
+                   print "Warning! Permissions/ownership no longer being tracked for file: $file\n";
+               }
+           }
+       }
+    }
+
+    # No merge conflicts -- write out perms/ownership data to .gitmeta file
+    unless ($stdout) {
+       open (OUT, ">$gitmeta.tmp") or die "Could not open $gitmeta.tmp for writing: $!\n";
+    }
+
+    my @files = `git-ls-files`;
+    my %dirs;
+
+    foreach my $path (@files) {
+       chomp $path;
+       # We have to manually add stats for parent directories
+       my $parent = dirname($path);
+       while (!exists $dirs{$parent}) {
+           $dirs{$parent} = 1;
+           next if $parent eq '.';
+           printstats($parent);
+           $parent = dirname($parent);
+       }
+       # Now the git-tracked file
+       printstats($path);
+    }
+
+    # diff the temporary metadata file to see if anything has changed
+    # If no metadata has changed, don't overwrite the real file
+    # This is just so `git commit -a` doesn't try to commit a bogus update
+    unless ($stdout) {
+       if (! -e $gitmeta) {
+           rename "$gitmeta.tmp", $gitmeta;
+       }
+       else {
+           my $diff = `diff -U 0 $gitmeta $gitmeta.tmp`;
+           if ($diff ne '') {
+               rename "$gitmeta.tmp", $gitmeta;
+           }
+           else {
+               unlink "$gitmeta.tmp";
+           }
+           if ($showdiff) {
+               print $diff;
+           }
+       }
+       close OUT;
+    }
+    # Make sure the .gitmeta file is tracked
+    system("git add $gitmeta");
+}
+
+
+sub printstats {
+    my $path = $_[0];
+    $path =~ s/@/\@/g;
+    my (undef,undef,$mode,undef,$uid,$gid) = lstat($path);
+    $path =~ s/%/\%/g;
+    if ($stdout) {
+       print $path;
+       printf "  mode=%04o  uid=$uid  gid=$gid\n", $mode & 07777;
+    }
+    else {
+       print OUT $path;
+       printf OUT "  mode=%04o  uid=$uid  gid=$gid\n", $mode & 07777;
+    }
+}
diff --git a/convert-objects.c b/convert-objects.c
deleted file mode 100644 (file)
index 90e7900..0000000
+++ /dev/null
@@ -1,329 +0,0 @@
-#include "cache.h"
-#include "blob.h"
-#include "commit.h"
-#include "tree.h"
-
-struct entry {
-       unsigned char old_sha1[20];
-       unsigned char new_sha1[20];
-       int converted;
-};
-
-#define MAXOBJECTS (1000000)
-
-static struct entry *convert[MAXOBJECTS];
-static int nr_convert;
-
-static struct entry * convert_entry(unsigned char *sha1);
-
-static struct entry *insert_new(unsigned char *sha1, int pos)
-{
-       struct entry *new = xcalloc(1, sizeof(struct entry));
-       hashcpy(new->old_sha1, sha1);
-       memmove(convert + pos + 1, convert + pos, (nr_convert - pos) * sizeof(struct entry *));
-       convert[pos] = new;
-       nr_convert++;
-       if (nr_convert == MAXOBJECTS)
-               die("you're kidding me - hit maximum object limit");
-       return new;
-}
-
-static struct entry *lookup_entry(unsigned char *sha1)
-{
-       int low = 0, high = nr_convert;
-
-       while (low < high) {
-               int next = (low + high) / 2;
-               struct entry *n = convert[next];
-               int cmp = hashcmp(sha1, n->old_sha1);
-               if (!cmp)
-                       return n;
-               if (cmp < 0) {
-                       high = next;
-                       continue;
-               }
-               low = next+1;
-       }
-       return insert_new(sha1, low);
-}
-
-static void convert_binary_sha1(void *buffer)
-{
-       struct entry *entry = convert_entry(buffer);
-       hashcpy(buffer, entry->new_sha1);
-}
-
-static void convert_ascii_sha1(void *buffer)
-{
-       unsigned char sha1[20];
-       struct entry *entry;
-
-       if (get_sha1_hex(buffer, sha1))
-               die("expected sha1, got '%s'", (char*) buffer);
-       entry = convert_entry(sha1);
-       memcpy(buffer, sha1_to_hex(entry->new_sha1), 40);
-}
-
-static unsigned int convert_mode(unsigned int mode)
-{
-       unsigned int newmode;
-
-       newmode = mode & S_IFMT;
-       if (S_ISREG(mode))
-               newmode |= (mode & 0100) ? 0755 : 0644;
-       return newmode;
-}
-
-static int write_subdirectory(void *buffer, unsigned long size, const char *base, int baselen, unsigned char *result_sha1)
-{
-       char *new = xmalloc(size);
-       unsigned long newlen = 0;
-       unsigned long used;
-
-       used = 0;
-       while (size) {
-               int len = 21 + strlen(buffer);
-               char *path = strchr(buffer, ' ');
-               unsigned char *sha1;
-               unsigned int mode;
-               char *slash, *origpath;
-
-               if (!path || strtoul_ui(buffer, 8, &mode))
-                       die("bad tree conversion");
-               mode = convert_mode(mode);
-               path++;
-               if (memcmp(path, base, baselen))
-                       break;
-               origpath = path;
-               path += baselen;
-               slash = strchr(path, '/');
-               if (!slash) {
-                       newlen += sprintf(new + newlen, "%o %s", mode, path);
-                       new[newlen++] = '\0';
-                       hashcpy((unsigned char*)new + newlen, (unsigned char *) buffer + len - 20);
-                       newlen += 20;
-
-                       used += len;
-                       size -= len;
-                       buffer = (char *) buffer + len;
-                       continue;
-               }
-
-               newlen += sprintf(new + newlen, "%o %.*s", S_IFDIR, (int)(slash - path), path);
-               new[newlen++] = 0;
-               sha1 = (unsigned char *)(new + newlen);
-               newlen += 20;
-
-               len = write_subdirectory(buffer, size, origpath, slash-origpath+1, sha1);
-
-               used += len;
-               size -= len;
-               buffer = (char *) buffer + len;
-       }
-
-       write_sha1_file(new, newlen, tree_type, result_sha1);
-       free(new);
-       return used;
-}
-
-static void convert_tree(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
-       void *orig_buffer = buffer;
-       unsigned long orig_size = size;
-
-       while (size) {
-               size_t len = 1+strlen(buffer);
-
-               convert_binary_sha1((char *) buffer + len);
-
-               len += 20;
-               if (len > size)
-                       die("corrupt tree object");
-               size -= len;
-               buffer = (char *) buffer + len;
-       }
-
-       write_subdirectory(orig_buffer, orig_size, "", 0, result_sha1);
-}
-
-static unsigned long parse_oldstyle_date(const char *buf)
-{
-       char c, *p;
-       char buffer[100];
-       struct tm tm;
-       const char *formats[] = {
-               "%c",
-               "%a %b %d %T",
-               "%Z",
-               "%Y",
-               " %Y",
-               NULL
-       };
-       /* We only ever did two timezones in the bad old format .. */
-       const char *timezones[] = {
-               "PDT", "PST", "CEST", NULL
-       };
-       const char **fmt = formats;
-
-       p = buffer;
-       while (isspace(c = *buf))
-               buf++;
-       while ((c = *buf++) != '\n')
-               *p++ = c;
-       *p++ = 0;
-       buf = buffer;
-       memset(&tm, 0, sizeof(tm));
-       do {
-               const char *next = strptime(buf, *fmt, &tm);
-               if (next) {
-                       if (!*next)
-                               return mktime(&tm);
-                       buf = next;
-               } else {
-                       const char **p = timezones;
-                       while (isspace(*buf))
-                               buf++;
-                       while (*p) {
-                               if (!memcmp(buf, *p, strlen(*p))) {
-                                       buf += strlen(*p);
-                                       break;
-                               }
-                               p++;
-                       }
-               }
-               fmt++;
-       } while (*buf && *fmt);
-       printf("left: %s\n", buf);
-       return mktime(&tm);
-}
-
-static int convert_date_line(char *dst, void **buf, unsigned long *sp)
-{
-       unsigned long size = *sp;
-       char *line = *buf;
-       char *next = strchr(line, '\n');
-       char *date = strchr(line, '>');
-       int len;
-
-       if (!next || !date)
-               die("missing or bad author/committer line %s", line);
-       next++; date += 2;
-
-       *buf = next;
-       *sp = size - (next - line);
-
-       len = date - line;
-       memcpy(dst, line, len);
-       dst += len;
-
-       /* Is it already in new format? */
-       if (isdigit(*date)) {
-               int datelen = next - date;
-               memcpy(dst, date, datelen);
-               return len + datelen;
-       }
-
-       /*
-        * Hacky hacky: one of the sparse old-style commits does not have
-        * any date at all, but we can fake it by using the committer date.
-        */
-       if (*date == '\n' && strchr(next, '>'))
-               date = strchr(next, '>')+2;
-
-       return len + sprintf(dst, "%lu -0700\n", parse_oldstyle_date(date));
-}
-
-static void convert_date(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
-       char *new = xmalloc(size + 100);
-       unsigned long newlen = 0;
-
-       /* "tree <sha1>\n" */
-       memcpy(new + newlen, buffer, 46);
-       newlen += 46;
-       buffer = (char *) buffer + 46;
-       size -= 46;
-
-       /* "parent <sha1>\n" */
-       while (!memcmp(buffer, "parent ", 7)) {
-               memcpy(new + newlen, buffer, 48);
-               newlen += 48;
-               buffer = (char *) buffer + 48;
-               size -= 48;
-       }
-
-       /* "author xyz <xyz> date" */
-       newlen += convert_date_line(new + newlen, &buffer, &size);
-       /* "committer xyz <xyz> date" */
-       newlen += convert_date_line(new + newlen, &buffer, &size);
-
-       /* Rest */
-       memcpy(new + newlen, buffer, size);
-       newlen += size;
-
-       write_sha1_file(new, newlen, commit_type, result_sha1);
-       free(new);
-}
-
-static void convert_commit(void *buffer, unsigned long size, unsigned char *result_sha1)
-{
-       void *orig_buffer = buffer;
-       unsigned long orig_size = size;
-
-       if (memcmp(buffer, "tree ", 5))
-               die("Bad commit '%s'", (char*) buffer);
-       convert_ascii_sha1((char *) buffer + 5);
-       buffer = (char *) buffer + 46;    /* "tree " + "hex sha1" + "\n" */
-       while (!memcmp(buffer, "parent ", 7)) {
-               convert_ascii_sha1((char *) buffer + 7);
-               buffer = (char *) buffer + 48;
-       }
-       convert_date(orig_buffer, orig_size, result_sha1);
-}
-
-static struct entry * convert_entry(unsigned char *sha1)
-{
-       struct entry *entry = lookup_entry(sha1);
-       enum object_type type;
-       void *buffer, *data;
-       unsigned long size;
-
-       if (entry->converted)
-               return entry;
-       data = read_sha1_file(sha1, &type, &size);
-       if (!data)
-               die("unable to read object %s", sha1_to_hex(sha1));
-
-       buffer = xmalloc(size);
-       memcpy(buffer, data, size);
-
-       if (type == OBJ_BLOB) {
-               write_sha1_file(buffer, size, blob_type, entry->new_sha1);
-       } else if (type == OBJ_TREE)
-               convert_tree(buffer, size, entry->new_sha1);
-       else if (type == OBJ_COMMIT)
-               convert_commit(buffer, size, entry->new_sha1);
-       else
-               die("unknown object type %d in %s", type, sha1_to_hex(sha1));
-       entry->converted = 1;
-       free(buffer);
-       free(data);
-       return entry;
-}
-
-int main(int argc, char **argv)
-{
-       unsigned char sha1[20];
-       struct entry *entry;
-
-       setup_git_directory();
-
-       if (argc != 2)
-               usage("git-convert-objects <sha1>");
-       if (get_sha1(argv[1], sha1))
-               die("Not a valid object name %s", argv[1]);
-
-       entry = convert_entry(sha1);
-       printf("new sha1: %s\n", sha1_to_hex(entry->new_sha1));
-       return 0;
-}
index 21908b10398492c0b07f705ed3b1ce7a06ac6b44..d77c8eb8b2802d675b320ddeb063c1cf70cc57d8 100644 (file)
--- a/convert.c
+++ b/convert.c
@@ -687,18 +687,3 @@ char *convert_to_working_tree(const char *path, const char *src, unsigned long *
 
        return buf;
 }
-
-void *convert_sha1_file(const char *path, const unsigned char *sha1,
-                        unsigned int mode, enum object_type *type,
-                        unsigned long *size)
-{
-       void *buffer = read_sha1_file(sha1, type, size);
-       if (S_ISREG(mode) && buffer) {
-               void *converted = convert_to_working_tree(path, buffer, size);
-               if (converted) {
-                       free(buffer);
-                       buffer = converted;
-               }
-       }
-       return buffer;
-}
index 0dde2f2dc032863b154509f5b966cfafb01dd722..9e440a9299b902bc96749ad2c86c08df6492eb1c 100644 (file)
@@ -115,7 +115,11 @@ static const unsigned int U[256] = {
 struct index_entry {
        const unsigned char *ptr;
        unsigned int val;
-       struct index_entry *next;
+};
+
+struct unpacked_index_entry {
+       struct index_entry entry;
+       struct unpacked_index_entry *next;
 };
 
 struct delta_index {
@@ -131,7 +135,8 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
        unsigned int i, hsize, hmask, entries, prev_val, *hash_count;
        const unsigned char *data, *buffer = buf;
        struct delta_index *index;
-       struct index_entry *entry, **hash;
+       struct unpacked_index_entry *entry, **hash;
+       struct index_entry *packed_entry, **packed_hash;
        void *mem;
        unsigned long memsize;
 
@@ -148,28 +153,21 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
        hmask = hsize - 1;
 
        /* allocate lookup index */
-       memsize = sizeof(*index) +
-                 sizeof(*hash) * hsize +
+       memsize = sizeof(*hash) * hsize +
                  sizeof(*entry) * entries;
        mem = malloc(memsize);
        if (!mem)
                return NULL;
-       index = mem;
-       mem = index + 1;
        hash = mem;
        mem = hash + hsize;
        entry = mem;
 
-       index->memsize = memsize;
-       index->src_buf = buf;
-       index->src_size = bufsize;
-       index->hash_mask = hmask;
        memset(hash, 0, hsize * sizeof(*hash));
 
        /* allocate an array to count hash entries */
        hash_count = calloc(hsize, sizeof(*hash_count));
        if (!hash_count) {
-               free(index);
+               free(hash);
                return NULL;
        }
 
@@ -183,12 +181,13 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
                        val = ((val << 8) | data[i]) ^ T[val >> RABIN_SHIFT];
                if (val == prev_val) {
                        /* keep the lowest of consecutive identical blocks */
-                       entry[-1].ptr = data + RABIN_WINDOW;
+                       entry[-1].entry.ptr = data + RABIN_WINDOW;
+                       --entries;
                } else {
                        prev_val = val;
                        i = val & hmask;
-                       entry->ptr = data + RABIN_WINDOW;
-                       entry->val = val;
+                       entry->entry.ptr = data + RABIN_WINDOW;
+                       entry->entry.val = val;
                        entry->next = hash[i];
                        hash[i] = entry++;
                        hash_count[i]++;
@@ -208,20 +207,84 @@ struct delta_index * create_delta_index(const void *buf, unsigned long bufsize)
         * the reference buffer.
         */
        for (i = 0; i < hsize; i++) {
-               if (hash_count[i] < HASH_LIMIT)
+               int acc;
+
+               if (hash_count[i] <= HASH_LIMIT)
                        continue;
+
+               entries -= hash_count[i] - HASH_LIMIT;
+               /* We leave exactly HASH_LIMIT entries in the bucket */
+
                entry = hash[i];
+               acc = 0;
                do {
-                       struct index_entry *keep = entry;
-                       int skip = hash_count[i] / HASH_LIMIT;
-                       do {
-                               entry = entry->next;
-                       } while(--skip && entry);
-                       keep->next = entry;
-               } while(entry);
+                       acc += hash_count[i] - HASH_LIMIT;
+                       if (acc > 0) {
+                               struct unpacked_index_entry *keep = entry;
+                               do {
+                                       entry = entry->next;
+                                       acc -= HASH_LIMIT;
+                               } while (acc > 0);
+                               keep->next = entry->next;
+                       }
+                       entry = entry->next;
+               } while (entry);
+
+               /* Assume that this loop is gone through exactly
+                * HASH_LIMIT times and is entered and left with
+                * acc==0.  So the first statement in the loop
+                * contributes (hash_count[i]-HASH_LIMIT)*HASH_LIMIT
+                * to the accumulator, and the inner loop consequently
+                * is run (hash_count[i]-HASH_LIMIT) times, removing
+                * one element from the list each time.  Since acc
+                * balances out to 0 at the final run, the inner loop
+                * body can't be left with entry==NULL.  So we indeed
+                * encounter entry==NULL in the outer loop only.
+                */
        }
        free(hash_count);
 
+       /* Now create the packed index in array form rather than
+        * linked lists */
+
+       memsize = sizeof(*index)
+               + sizeof(*packed_hash) * (hsize+1)
+               + sizeof(*packed_entry) * entries;
+
+       mem = malloc(memsize);
+
+       if (!mem) {
+               free(hash);
+               return NULL;
+       }
+
+       index = mem;
+       index->memsize = memsize;
+       index->src_buf = buf;
+       index->src_size = bufsize;
+       index->hash_mask = hmask;
+
+       mem = index + 1;
+       packed_hash = mem;
+       mem = packed_hash + (hsize+1);
+       packed_entry = mem;
+
+       /* Coalesce all entries belonging to one linked list into
+        * consecutive array entries */
+
+       for (i = 0; i < hsize; i++) {
+               packed_hash[i] = packed_entry;
+               for (entry = hash[i]; entry; entry = entry->next)
+                       *packed_entry++ = entry->entry;
+       }
+
+       /* Sentinel value to indicate the length of the last hash
+        * bucket */
+
+       packed_hash[hsize] = packed_entry;
+       assert(packed_entry - (struct index_entry *)mem == entries);
+       free(hash);
+
        return index;
 }
 
@@ -302,7 +365,7 @@ create_delta(const struct delta_index *index,
                        val ^= U[data[-RABIN_WINDOW]];
                        val = ((val << 8) | *data) ^ T[val >> RABIN_SHIFT];
                        i = val & index->hash_mask;
-                       for (entry = index->hash[i]; entry; entry = entry->next) {
+                       for (entry = index->hash[i]; entry < index->hash[i+1]; entry++) {
                                const unsigned char *ref = entry->ptr;
                                const unsigned char *src = data;
                                unsigned int ref_size = ref_top - ref;
diff --git a/diff.c b/diff.c
index 0ee9ea1c1b47b82710a3850e7e9f679e4486f7bd..35e3c619864809a46cd5b6a36d08e8103b5ce485 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -1675,7 +1675,7 @@ int diff_populate_filespec(struct diff_filespec *s, int size_only)
        return 0;
 }
 
-void diff_free_filespec_data(struct diff_filespec *s)
+void diff_free_filespec_data_large(struct diff_filespec *s)
 {
        if (s->should_free)
                free(s->data);
@@ -1686,6 +1686,11 @@ void diff_free_filespec_data(struct diff_filespec *s)
                s->should_free = s->should_munmap = 0;
                s->data = NULL;
        }
+}
+
+void diff_free_filespec_data(struct diff_filespec *s)
+{
+       diff_free_filespec_data_large(s);
        free(s->cnt_data);
        s->cnt_data = NULL;
 }
index 41b35c3a9e6935f6cd8563de732321e74e115765..4fc200064ae655c6936828c6bf04234afde778ce 100644 (file)
@@ -184,7 +184,8 @@ static int estimate_similarity(struct diff_filespec *src,
        if (base_size * (MAX_SCORE-minimum_score) < delta_size * MAX_SCORE)
                return 0;
 
-       if (diff_populate_filespec(src, 0) || diff_populate_filespec(dst, 0))
+       if ((!src->cnt_data && diff_populate_filespec(src, 0))
+               || (!dst->cnt_data && diff_populate_filespec(dst, 0)))
                return 0; /* error but caught downstream */
 
 
@@ -377,10 +378,10 @@ void diffcore_rename(struct diff_options *options)
                        m->score = estimate_similarity(one, two,
                                                       minimum_score);
                        m->name_score = basename_same(one, two);
-                       diff_free_filespec_data(one);
+                       diff_free_filespec_data_large(one);
                }
                /* We do not need the text anymore */
-               diff_free_filespec_data(two);
+               diff_free_filespec_data_large(two);
                dst_cnt++;
        }
        /* cost matrix sorted by most to least similar pair */
index eef17c4ca2e81c572fb110e9eb11e8ed9d51f9a0..4bf175bda99f51f8df1445349d87bc59b56095e7 100644 (file)
@@ -48,6 +48,7 @@ extern void fill_filespec(struct diff_filespec *, const unsigned char *,
 
 extern int diff_populate_filespec(struct diff_filespec *, int);
 extern void diff_free_filespec_data(struct diff_filespec *);
+extern void diff_free_filespec_data_large(struct diff_filespec *);
 extern int diff_filespec_is_binary(struct diff_filespec *);
 
 struct diff_filepair {
index b66173c0cd8e7908c1ac9ffc9c9a6a91160849a0..32c46d7ed4b26220f4c9e7fc778bb240c85dae1c 100755 (executable)
--- a/git-am.sh
+++ b/git-am.sh
@@ -62,10 +62,8 @@ fall_back_3way () {
     mkdir "$dotest/patch-merge-tmp-dir"
 
     # First see if the patch records the index info that we can use.
-    git apply -z --index-info "$dotest/patch" \
-       >"$dotest/patch-merge-index-info" &&
-    GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
-    git update-index -z --index-info <"$dotest/patch-merge-index-info" &&
+    git apply --build-fake-ancestor "$dotest/patch-merge-tmp-index" \
+       "$dotest/patch" &&
     GIT_INDEX_FILE="$dotest/patch-merge-tmp-index" \
     git write-tree >"$dotest/patch-merge-base+" ||
     cannot_fallback "Repository lacks necessary blobs to fall back on 3-way merge."
index 7a7a2cb4b47c62d4826aabd9d90a7af26427f980..cb14f0621651d2006b08d1eddf67ab3269df84d0 100755 (executable)
@@ -98,101 +98,71 @@ do
                no_edit=t
                log_given=t$log_given
                logfile="$1"
-               shift
                ;;
        -F*|-f*)
                no_edit=t
                log_given=t$log_given
-               logfile=`expr "z$1" : 'z-[Ff]\(.*\)'`
-               shift
+               logfile="${1#-[Ff]}"
                ;;
        --F=*|--f=*|--fi=*|--fil=*|--file=*)
                no_edit=t
                log_given=t$log_given
-               logfile=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-               shift
+               logfile="${1#*=}"
                ;;
        -a|--a|--al|--all)
                all=t
-               shift
                ;;
        --au=*|--aut=*|--auth=*|--autho=*|--author=*)
-               force_author=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-               shift
+               force_author="${1#*=}"
                ;;
        --au|--aut|--auth|--autho|--author)
                case "$#" in 1) usage ;; esac
                shift
                force_author="$1"
-               shift
                ;;
        -e|--e|--ed|--edi|--edit)
                edit_flag=t
-               shift
                ;;
        -i|--i|--in|--inc|--incl|--inclu|--includ|--include)
                also=t
-               shift
                ;;
        --int|--inte|--inter|--intera|--interac|--interact|--interacti|\
        --interactiv|--interactive)
                interactive=t
-               shift
                ;;
        -o|--o|--on|--onl|--only)
                only=t
-               shift
                ;;
        -m|--m|--me|--mes|--mess|--messa|--messag|--message)
                case "$#" in 1) usage ;; esac
                shift
                log_given=m$log_given
-               if test "$log_message" = ''
-               then
-                   log_message="$1"
-               else
-                   log_message="$log_message
+               log_message="${log_message:+${log_message}
 
-$1"
-               fi
+}$1"
                no_edit=t
-               shift
                ;;
        -m*)
                log_given=m$log_given
-               if test "$log_message" = ''
-               then
-                   log_message=`expr "z$1" : 'z-m\(.*\)'`
-               else
-                   log_message="$log_message
+               log_message="${log_message:+${log_message}
 
-`expr "z$1" : 'z-m\(.*\)'`"
-               fi
+}${1#-m}"
                no_edit=t
-               shift
                ;;
        --m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*)
                log_given=m$log_given
-               if test "$log_message" = ''
-               then
-                   log_message=`expr "z$1" : 'z-[^=]*=\(.*\)'`
-               else
-                   log_message="$log_message
+               log_message="${log_message:+${log_message}
 
-`expr "z$1" : 'zq-[^=]*=\(.*\)'`"
-               fi
+}${1#*=}"
                no_edit=t
-               shift
                ;;
        -n|--n|--no|--no-|--no-v|--no-ve|--no-ver|--no-veri|--no-verif|\
        --no-verify)
                verify=
-               shift
                ;;
        --a|--am|--ame|--amen|--amend)
                amend=t
                use_commit=HEAD
-               shift
                ;;
        -c)
                case "$#" in 1) usage ;; esac
@@ -200,15 +170,13 @@ $1"
                log_given=t$log_given
                use_commit="$1"
                no_edit=
-               shift
                ;;
        --ree=*|--reed=*|--reedi=*|--reedit=*|--reedit-=*|--reedit-m=*|\
        --reedit-me=*|--reedit-mes=*|--reedit-mess=*|--reedit-messa=*|\
        --reedit-messag=*|--reedit-message=*)
                log_given=t$log_given
-               use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+               use_commit="${1#*=}"
                no_edit=
-               shift
                ;;
        --ree|--reed|--reedi|--reedit|--reedit-|--reedit-m|--reedit-me|\
        --reedit-mes|--reedit-mess|--reedit-messa|--reedit-messag|\
@@ -218,7 +186,6 @@ $1"
                log_given=t$log_given
                use_commit="$1"
                no_edit=
-               shift
                ;;
        -C)
                case "$#" in 1) usage ;; esac
@@ -226,15 +193,13 @@ $1"
                log_given=t$log_given
                use_commit="$1"
                no_edit=t
-               shift
                ;;
        --reu=*|--reus=*|--reuse=*|--reuse-=*|--reuse-m=*|--reuse-me=*|\
        --reuse-mes=*|--reuse-mess=*|--reuse-messa=*|--reuse-messag=*|\
        --reuse-message=*)
                log_given=t$log_given
-               use_commit=`expr "z$1" : 'z-[^=]*=\(.*\)'`
+               use_commit="${1#*=}"
                no_edit=t
-               shift
                ;;
        --reu|--reus|--reuse|--reuse-|--reuse-m|--reuse-me|--reuse-mes|\
        --reuse-mess|--reuse-messa|--reuse-messag|--reuse-message)
@@ -243,32 +208,26 @@ $1"
                log_given=t$log_given
                use_commit="$1"
                no_edit=t
-               shift
                ;;
        -s|--s|--si|--sig|--sign|--signo|--signof|--signoff)
                signoff=t
-               shift
                ;;
        -t|--t|--te|--tem|--temp|--templ|--templa|--templat|--template)
                case "$#" in 1) usage ;; esac
                shift
                templatefile="$1"
                no_edit=
-               shift
                ;;
        -q|--q|--qu|--qui|--quie|--quiet)
                quiet=t
-               shift
                ;;
        -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose)
                verbose=t
-               shift
                ;;
        -u|--u|--un|--unt|--untr|--untra|--untrac|--untrack|--untracke|\
        --untracked|--untracked-|--untracked-f|--untracked-fi|--untracked-fil|\
        --untracked-file|--untracked-files)
                untracked_files=t
-               shift
                ;;
        --)
                shift
@@ -281,6 +240,7 @@ $1"
                break
                ;;
        esac
+       shift
 done
 case "$edit_flag" in t) no_edit= ;; esac
 
@@ -441,12 +401,8 @@ esac
 
 if test t = "$verify" && test -x "$GIT_DIR"/hooks/pre-commit
 then
-       if test "$TMP_INDEX"
-       then
-               GIT_INDEX_FILE="$TMP_INDEX" "$GIT_DIR"/hooks/pre-commit
-       else
-               GIT_INDEX_FILE="$USE_INDEX" "$GIT_DIR"/hooks/pre-commit
-       fi || exit
+    GIT_INDEX_FILE="${TMP_INDEX:-${USE_INDEX}}" "$GIT_DIR"/hooks/pre-commit \
+    || exit
 fi
 
 if test "$log_message" != ''
index ca0a597a282328bff9e0e29fd3653dbd656489fb..1bfbdeb94f55d57b429b91aa8762618153c34f7f 100644 (file)
@@ -172,6 +172,12 @@ extern uintmax_t gitstrtoumax(const char *, char **, int);
 extern const char *githstrerror(int herror);
 #endif
 
+#ifdef NO_MEMMEM
+#define memmem gitmemmem
+void *gitmemmem(const void *haystack, size_t haystacklen,
+                const void *needle, size_t needlelen);
+#endif
+
 extern void release_pack_memory(size_t, int);
 
 static inline char* xstrdup(const char *str)
index cde09d4d602811b610e0d744f4e8ede6f9fb0a39..6c513dcbdf44036b0207c276e765a87eceb7aa77 100755 (executable)
@@ -97,6 +97,19 @@ finish () {
                fi
                ;;
        esac
+
+       # Run a post-merge hook
+        if test -x "$GIT_DIR"/hooks/post-merge
+        then
+           case "$squash" in
+           t)
+                "$GIT_DIR"/hooks/post-merge 1
+               ;;
+           '')
+                "$GIT_DIR"/hooks/post-merge 0
+               ;;
+           esac
+        fi
 }
 
 merge_name () {
index a0e44f71c4acd5d995bf823913ba63e119ed2e68..9f4f3134b60174495c23968cf1ca01ffdbc49bdf 100755 (executable)
@@ -12,6 +12,7 @@ USAGE='[--tool=tool] [file to merge] ...'
 SUBDIRECTORY_OK=Yes
 . git-sh-setup
 require_work_tree
+prefix=$(git rev-parse --show-prefix)
 
 # Returns true if the mode reflects a symlink
 is_symlink () {
@@ -162,9 +163,9 @@ merge_file () {
     local_mode=`git ls-files -u -- "$path" | awk '{if ($3==2) print $1;}'`
     remote_mode=`git ls-files -u -- "$path" | awk '{if ($3==3) print $1;}'`
 
-    base_present   && git cat-file blob ":1:$path" > "$BASE" 2>/dev/null
-    local_present  && git cat-file blob ":2:$path" > "$LOCAL" 2>/dev/null
-    remote_present && git cat-file blob ":3:$path" > "$REMOTE" 2>/dev/null
+    base_present   && git cat-file blob ":1:$prefix$path" >"$BASE" 2>/dev/null
+    local_present  && git cat-file blob ":2:$prefix$path" >"$LOCAL" 2>/dev/null
+    remote_present && git cat-file blob ":3:$prefix$path" >"$REMOTE" 2>/dev/null
 
     if test -z "$local_mode" -o -z "$remote_mode"; then
        echo "Deleted merge conflict for '$path':"
@@ -191,10 +192,10 @@ merge_file () {
     case "$merge_tool" in
        kdiff3)
            if base_present ; then
-               (kdiff3 --auto --L1 "$path (Base)" -L2 "$path (Local)" --L3 "$path (Remote)" \
+               (kdiff3 --auto --L1 "$path (Base)" --L2 "$path (Local)" --L3 "$path (Remote)" \
                    -o "$path" -- "$BASE" "$LOCAL" "$REMOTE" > /dev/null 2>&1)
            else
-               (kdiff3 --auto -L1 "$path (Local)" --L2 "$path (Remote)" \
+               (kdiff3 --auto --L1 "$path (Local)" --L2 "$path (Remote)" \
                    -o "$path" -- "$LOCAL" "$REMOTE" > /dev/null 2>&1)
            fi
            status=$?
@@ -251,9 +252,9 @@ merge_file () {
            ;;
        emerge)
            if base_present ; then
-               emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$path"
+               emacs -f emerge-files-with-ancestor-command "$LOCAL" "$REMOTE" "$BASE" "$(basename "$path")"
            else
-               emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$path"
+               emacs -f emerge-files-command "$LOCAL" "$REMOTE" "$(basename "$path")"
            fi
            status=$?
            save_backup
index 74a54d5d08f86ebbd6a4b3af1f6950dbfd91c743..880c81d121bfb9555c729bc299f8ae8baf1db32c 100755 (executable)
@@ -71,6 +71,10 @@ commit=$(git rev-parse HEAD)
 
 mkdir $tmp_dir || exit 2
 for patch_name in $(grep -v '^#' < "$QUILT_PATCHES/series" ); do
+       if ! [ -f "$QUILT_PATCHES/$patch_name" ] ; then
+               echo "$patch_name doesn't exist. Skipping."
+               continue
+       fi
        echo $patch_name
        git mailinfo "$tmp_msg" "$tmp_patch" \
                <"$QUILT_PATCHES/$patch_name" >"$tmp_info" || exit 3
index 2fa53fdaeb3a24495f023e1f1f79da23e7e34f7c..268a629c434c3cc1bad8a59861f3f093291ec540 100755 (executable)
@@ -36,14 +36,14 @@ warn () {
 output () {
        case "$VERBOSE" in
        '')
-               "$@" > "$DOTEST"/output 2>&1
+               output=$("$@" 2>&1 )
                status=$?
-               test $status != 0 &&
-                       cat "$DOTEST"/output
+               test $status != 0 && printf "%s\n" "$output"
                return $status
-       ;;
+               ;;
        *)
                "$@"
+               ;;
        esac
 }
 
@@ -63,6 +63,7 @@ comment_for_reflog () {
        ''|rebase*)
                GIT_REFLOG_ACTION="rebase -i ($1)"
                export GIT_REFLOG_ACTION
+               ;;
        esac
 }
 
@@ -70,22 +71,23 @@ mark_action_done () {
        sed -e 1q < "$TODO" >> "$DONE"
        sed -e 1d < "$TODO" >> "$TODO".new
        mv -f "$TODO".new "$TODO"
-       count=$(($(wc -l < "$DONE")))
-       total=$(($count+$(wc -l < "$TODO")))
+       count=$(($(grep -ve '^$' -e '^#' < "$DONE" | wc -l)))
+       total=$(($count+$(grep -ve '^$' -e '^#' < "$TODO" | wc -l)))
        printf "Rebasing (%d/%d)\r" $count $total
        test -z "$VERBOSE" || echo
 }
 
 make_patch () {
-       parent_sha1=$(git rev-parse --verify "$1"^ 2> /dev/null)
+       parent_sha1=$(git rev-parse --verify "$1"^) ||
+               die "Cannot get patch for $1^"
        git diff "$parent_sha1".."$1" > "$DOTEST"/patch
+       test -f "$DOTEST"/message ||
+               git cat-file commit "$1" | sed "1,/^$/d" > "$DOTEST"/message
+       test -f "$DOTEST"/author-script ||
+               get_author_ident_from_commit "$1" > "$DOTEST"/author-script
 }
 
 die_with_patch () {
-       test -f "$DOTEST"/message ||
-               git cat-file commit $sha1 | sed "1,/^$/d" > "$DOTEST"/message
-       test -f "$DOTEST"/author-script ||
-               get_author_ident_from_commit $sha1 > "$DOTEST"/author-script
        make_patch "$1"
        die "$2"
 }
@@ -95,13 +97,18 @@ die_abort () {
        die "$1"
 }
 
+has_action () {
+       grep -vqe '^$' -e '^#' "$1"
+}
+
 pick_one () {
        no_ff=
        case "$1" in -n) sha1=$2; no_ff=t ;; *) sha1=$1 ;; esac
        output git rev-parse --verify $sha1 || die "Invalid commit name: $sha1"
        test -d "$REWRITTEN" &&
                pick_one_preserving_merges "$@" && return
-       parent_sha1=$(git rev-parse --verify $sha1^ 2>/dev/null)
+       parent_sha1=$(git rev-parse --verify $sha1^) ||
+               die "Could not get the parent of $sha1"
        current_sha1=$(git rev-parse --verify HEAD)
        if test $no_ff$current_sha1 = $parent_sha1; then
                output git reset --hard $sha1
@@ -129,7 +136,7 @@ pick_one_preserving_merges () {
        fast_forward=t
        preserve=t
        new_parents=
-       for p in $(git rev-list --parents -1 $sha1 | cut -d -f2-)
+       for p in $(git rev-list --parents -1 $sha1 | cut -d' ' -f2-)
        do
                if test -f "$REWRITTEN"/$p
                then
@@ -141,41 +148,47 @@ pick_one_preserving_merges () {
                                ;; # do nothing; that parent is already there
                        *)
                                new_parents="$new_parents $new_p"
+                               ;;
                        esac
                fi
        done
        case $fast_forward in
        t)
                output warn "Fast forward to $sha1"
-               test $preserve=f && echo $sha1 > "$REWRITTEN"/$sha1
+               test $preserve = f || echo $sha1 > "$REWRITTEN"/$sha1
                ;;
        f)
                test "a$1" = a-n && die "Refusing to squash a merge: $sha1"
 
-               first_parent=$(expr "$new_parents" : " \([^ ]*\)")
+               first_parent=$(expr "$new_parents" : ' \([^ ]*\)')
                # detach HEAD to current parent
                output git checkout $first_parent 2> /dev/null ||
                        die "Cannot move HEAD to $first_parent"
 
                echo $sha1 > "$DOTEST"/current-commit
                case "$new_parents" in
-               \ *\ *)
+               ' '*' '*)
                        # redo merge
                        author_script=$(get_author_ident_from_commit $sha1)
                        eval "$author_script"
-                       msg="$(git cat-file commit $sha1 | \
-                               sed -e '1,/^$/d' -e "s/[\"\\]/\\\\&/g")"
+                       msg="$(git cat-file commit $sha1 | sed -e '1,/^$/d')"
                        # NEEDSWORK: give rerere a chance
-                       if ! output git merge $STRATEGY -m "$msg" $new_parents
+                       if ! GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
+                               GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
+                               GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
+                               output git merge $STRATEGY -m "$msg" \
+                                       $new_parents
                        then
-                               echo "$msg" > "$GIT_DIR"/MERGE_MSG
+                               printf "%s\n" "$msg" > "$GIT_DIR"/MERGE_MSG
                                die Error redoing merge $sha1
                        fi
                        ;;
                *)
                        output git cherry-pick $STRATEGY "$@" ||
                                die_with_patch $sha1 "Could not pick $sha1"
+                       ;;
                esac
+               ;;
        esac
 }
 
@@ -212,11 +225,11 @@ peek_next_command () {
 }
 
 do_next () {
-       test -f "$DOTEST"/message && rm "$DOTEST"/message
-       test -f "$DOTEST"/author-script && rm "$DOTEST"/author-script
+       rm -f "$DOTEST"/message "$DOTEST"/author-script \
+               "$DOTEST"/amend || exit
        read command sha1 rest < "$TODO"
        case "$command" in
-       \#|'')
+       '#'*|'')
                mark_action_done
                ;;
        pick)
@@ -233,6 +246,7 @@ do_next () {
                pick_one $sha1 ||
                        die_with_patch $sha1 "Could not apply $sha1... $rest"
                make_patch $sha1
+               : > "$DOTEST"/amend
                warn
                warn "You can amend the commit now, with"
                warn
@@ -243,7 +257,7 @@ do_next () {
        squash)
                comment_for_reflog squash
 
-               test -z "$(grep -ve '^$' -e '^#' < $DONE)" &&
+               has_action "$DONE" ||
                        die "Cannot 'squash' without a previous commit"
 
                mark_action_done
@@ -253,11 +267,12 @@ do_next () {
                        EDIT_COMMIT=
                        USE_OUTPUT=output
                        cp "$MSG" "$SQUASH_MSG"
-               ;;
+                       ;;
                *)
                        EDIT_COMMIT=-e
                        USE_OUTPUT=
-                       test -f "$SQUASH_MSG" && rm "$SQUASH_MSG"
+                       rm -f "$SQUASH_MSG" || exit
+                       ;;
                esac
 
                failed=f
@@ -269,7 +284,9 @@ do_next () {
                f)
                        # This is like --amend, but with a different message
                        eval "$author_script"
-                       export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_AUTHOR_DATE
+                       GIT_AUTHOR_NAME="$GIT_AUTHOR_NAME" \
+                       GIT_AUTHOR_EMAIL="$GIT_AUTHOR_EMAIL" \
+                       GIT_AUTHOR_DATE="$GIT_AUTHOR_DATE" \
                        $USE_OUTPUT git commit -F "$MSG" $EDIT_COMMIT
                        ;;
                t)
@@ -277,11 +294,13 @@ do_next () {
                        warn
                        warn "Could not apply $sha1... $rest"
                        die_with_patch $sha1 ""
+                       ;;
                esac
                ;;
        *)
                warn "Unknown command: $command $sha1 $rest"
                die_with_patch $sha1 "Please fix this in the file $TODO."
+               ;;
        esac
        test -s "$TODO" && return
 
@@ -330,7 +349,9 @@ do
                git update-index --refresh &&
                git diff-files --quiet &&
                ! git diff-index --cached --quiet HEAD &&
-               . "$DOTEST"/author-script &&
+               . "$DOTEST"/author-script && {
+                       test ! -f "$DOTEST"/amend || git reset --soft HEAD^
+               } &&
                export GIT_AUTHOR_NAME GIT_AUTHOR_NAME GIT_AUTHOR_DATE &&
                git commit -F "$DOTEST"/message -e
 
@@ -406,7 +427,6 @@ do
 
                require_clean_work_tree
 
-               mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
                if test ! -z "$2"
                then
                        output git show-ref --verify --quiet "refs/heads/$2" ||
@@ -418,6 +438,8 @@ do
                HEAD=$(git rev-parse --verify HEAD) || die "No HEAD?"
                UPSTREAM=$(git rev-parse --verify "$1") || die "Invalid base"
 
+               mkdir "$DOTEST" || die "Could not create temporary $DOTEST"
+
                test -z "$ONTO" && ONTO=$UPSTREAM
 
                : > "$DOTEST"/interactive || die "Could not mark as interactive"
@@ -468,17 +490,18 @@ EOF
                        $UPSTREAM...$HEAD | \
                        sed -n "s/^>/pick /p" >> "$TODO"
 
-               test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+               has_action "$TODO" ||
                        die_abort "Nothing to do"
 
                cp "$TODO" "$TODO".backup
                git_editor "$TODO" ||
                        die "Could not execute editor"
 
-               test -z "$(grep -ve '^$' -e '^#' < $TODO)" &&
+               has_action "$TODO" ||
                        die_abort "Nothing to do"
 
                output git checkout $ONTO && do_rest
+               ;;
        esac
        shift
 done
index 058fcacb7eea33128436fa04c0412370562e9ff0..1583402a060793c25e49c3446c2a35fe27101883 100755 (executable)
@@ -215,9 +215,11 @@ do
        -v|--verbose)
                verbose=t
                ;;
+       --whitespace=*)
+               git_am_opt="$git_am_opt $1"
+               ;;
        -C*)
-               git_am_opt=$1
-               shift
+               git_am_opt="$git_am_opt $1"
                ;;
        -*)
                usage
index 01cf480221be1e5860bd701d5d17ced25766d38d..b7c1e01d7d7d5dfb68870d65c0f0419138ccc506 100755 (executable)
@@ -278,7 +278,9 @@ sub add_remote {
 
        for (@$track) {
                $git->command('config', '--add', "remote.$name.fetch",
-                             "+refs/heads/$_:refs/remotes/$name/$_");
+                               $opts->{'mirror'} ?
+                               "+refs/$_:refs/$_" :
+                               "+refs/heads/$_:refs/remotes/$name/$_");
        }
        if ($opts->{'fetch'}) {
                $git->command('fetch', $name);
@@ -314,6 +316,34 @@ sub update_remote {
        }
 }
 
+sub rm_remote {
+       my ($name) = @_;
+       if (!exists $remote->{$name}) {
+               print STDERR "No such remote $name\n";
+               return;
+       }
+
+       $git->command('config', '--remove-section', "remote.$name");
+
+       eval {
+           my @trackers = $git->command('config', '--get-regexp',
+                       'branch.*.remote', $name);
+               for (@trackers) {
+                       /^branch\.(.*)?\.remote/;
+                       $git->config('--unset', "branch.$1.remote");
+                       $git->config('--unset', "branch.$1.merge");
+               }
+       };
+
+
+       my @refs = $git->command('for-each-ref',
+               '--format=%(refname) %(objectname)', "refs/remotes/$name");
+       for (@refs) {
+               ($ref, $object) = split;
+               $git->command(qw(update-ref -d), $ref, $object);
+       }
+}
+
 sub add_usage {
        print STDERR "Usage: git remote add [-f] [-t track]* [-m master] <name> <url>\n";
        exit(1);
@@ -409,6 +439,10 @@ sub add_usage {
                        shift @ARGV;
                        next;
                }
+               if ($opt eq '--mirror') {
+                       $opts{'mirror'} = 1;
+                       next;
+               }
                add_usage();
        }
        if (@ARGV != 3) {
@@ -416,9 +450,17 @@ sub add_usage {
        }
        add_remote($ARGV[1], $ARGV[2], \%opts);
 }
+elsif ($ARGV[0] eq 'rm') {
+       if (@ARGV <= 1) {
+               print STDERR "Usage: git remote rm <remote>\n";
+               exit(1);
+       }
+       rm_remote($ARGV[1]);
+}
 else {
        print STDERR "Usage: git remote\n";
        print STDERR "       git remote add <name> <url>\n";
+       print STDERR "       git remote rm <name>\n";
        print STDERR "       git remote show <name>\n";
        print STDERR "       git remote prune <name>\n";
        print STDERR "       git remote update [group]\n";
diff --git a/git-reset.sh b/git-reset.sh
deleted file mode 100755 (executable)
index bafeb52..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-#!/bin/sh
-#
-# Copyright (c) 2005, 2006 Linus Torvalds and Junio C Hamano
-#
-USAGE='[--mixed | --soft | --hard]  [<commit-ish>] [ [--] <paths>...]'
-SUBDIRECTORY_OK=Yes
-. git-sh-setup
-set_reflog_action "reset $*"
-require_work_tree
-
-update= reset_type=--mixed
-unset rev
-
-while test $# != 0
-do
-       case "$1" in
-       --mixed | --soft | --hard)
-               reset_type="$1"
-               ;;
-       --)
-               break
-               ;;
-       -*)
-               usage
-               ;;
-       *)
-               rev=$(git rev-parse --verify "$1") || exit
-               shift
-               break
-               ;;
-       esac
-       shift
-done
-
-: ${rev=HEAD}
-rev=$(git rev-parse --verify $rev^0) || exit
-
-# Skip -- in "git reset HEAD -- foo" and "git reset -- foo".
-case "$1" in --) shift ;; esac
-
-# git reset --mixed tree [--] paths... can be used to
-# load chosen paths from the tree into the index without
-# affecting the working tree nor HEAD.
-if test $# != 0
-then
-       test "$reset_type" = "--mixed" ||
-               die "Cannot do partial $reset_type reset."
-
-       git diff-index --cached $rev -- "$@" |
-       sed -e 's/^:\([0-7][0-7]*\) [0-7][0-7]* \([0-9a-f][0-9a-f]*\) [0-9a-f][0-9a-f]* [A-Z]   \(.*\)$/\1 \2   \3/' |
-       git update-index --add --remove --index-info || exit
-       git update-index --refresh
-       exit
-fi
-
-cd_to_toplevel
-
-if test "$reset_type" = "--hard"
-then
-       update=-u
-fi
-
-# Soft reset does not touch the index file nor the working tree
-# at all, but requires them in a good order.  Other resets reset
-# the index file to the tree object we are switching to.
-if test "$reset_type" = "--soft"
-then
-       if test -f "$GIT_DIR/MERGE_HEAD" ||
-          test "" != "$(git ls-files --unmerged)"
-       then
-               die "Cannot do a soft reset in the middle of a merge."
-       fi
-else
-       git read-tree -v --reset $update "$rev" || exit
-fi
-
-# Any resets update HEAD to the head being switched to.
-if orig=$(git rev-parse --verify HEAD 2>/dev/null)
-then
-       echo "$orig" >"$GIT_DIR/ORIG_HEAD"
-else
-       rm -f "$GIT_DIR/ORIG_HEAD"
-fi
-git update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev"
-update_ref_status=$?
-
-case "$reset_type" in
---hard )
-       test $update_ref_status = 0 && {
-               printf "HEAD is now at "
-               GIT_PAGER= git log --max-count=1 --pretty=oneline \
-                       --abbrev-commit HEAD
-       }
-       ;;
---soft )
-       ;; # Nothing else to do
---mixed )
-       # Report what has not been updated.
-       git update-index --refresh
-       ;;
-esac
-
-rm -f "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/rr-cache/MERGE_RR" \
-       "$GIT_DIR/SQUASH_MSG" "$GIT_DIR/MERGE_MSG"
-
-exit $update_ref_status
index 9547cc37a1c4fd326876e26b081bc6d6a5141ce6..4031e86b8688e62cc3a547ffd52dfc1b8234f0f4 100755 (executable)
@@ -73,9 +73,18 @@ sub usage {
    --signed-off-cc Automatically add email addresses that appear in
                  Signed-off-by: or Cc: lines to the cc: list. Defaults to on.
 
+   --identity     The configuration identity, a subsection to prioritise over
+                  the default section.
+
    --smtp-server  If set, specifies the outgoing SMTP server to use.
                   Defaults to localhost.
 
+   --smtp-user    The username for SMTP-AUTH.
+
+   --smtp-pass    The password for SMTP-AUTH.
+
+   --smtp-ssl     If set, connects to the SMTP server using SSL.
+
    --suppress-from Suppress sending emails to yourself if your address
                   appears in a From: line. Defaults to off.
 
@@ -145,7 +154,6 @@ sub format_2822_time {
 my (@to,@cc,@initial_cc,@bcclist,@xh,
        $initial_reply_to,$initial_subject,@files,$author,$sender,$compose,$time);
 
-my $smtp_server;
 my $envelope_sender;
 
 # Example reply to:
@@ -164,24 +172,26 @@ sub format_2822_time {
 
 # Variables with corresponding config settings
 my ($thread, $chain_reply_to, $suppress_from, $signed_off_cc, $cc_cmd);
+my ($smtp_server, $smtp_authuser, $smtp_authpass, $smtp_ssl);
+my ($identity, $aliasfiletype, @alias_files);
 
-my %config_settings = (
+my %config_bool_settings = (
     "thread" => [\$thread, 1],
     "chainreplyto" => [\$chain_reply_to, 1],
     "suppressfrom" => [\$suppress_from, 0],
     "signedoffcc" => [\$signed_off_cc, 1],
-    "cccmd" => [\$cc_cmd, ""],
+    "smtpssl" => [\$smtp_ssl, 0],
 );
 
-foreach my $setting (keys %config_settings) {
-    my $config = $repo->config_bool("sendemail.$setting");
-    ${$config_settings{$setting}->[0]} = (defined $config) ? $config : $config_settings{$setting}->[1];
-}
-
-@bcclist = $repo->config('sendemail.bcc');
-if (!@bcclist or !$bcclist[0]) {
-    @bcclist = ();
-}
+my %config_settings = (
+    "smtpserver" => \$smtp_server,
+    "smtpuser" => \$smtp_authuser,
+    "smtppass" => \$smtp_authpass,
+    "cccmd" => \$cc_cmd,
+    "aliasfiletype" => \$aliasfiletype,
+    "bcc" => \@bcclist,
+    "aliasesfile" => \@alias_files,
+);
 
 # Begin by accumulating all the variables (defined above), that we will end up
 # needing, first, from the command line:
@@ -194,6 +204,10 @@ sub format_2822_time {
                    "bcc=s" => \@bcclist,
                    "chain-reply-to!" => \$chain_reply_to,
                    "smtp-server=s" => \$smtp_server,
+                   "smtp-user=s" => \$smtp_authuser,
+                   "smtp-pass=s" => \$smtp_authpass,
+                   "smtp-ssl!" => \$smtp_ssl,
+                   "identity=s" => \$identity,
                    "compose" => \$compose,
                    "quiet" => \$quiet,
                    "cc-cmd=s" => \$cc_cmd,
@@ -208,6 +222,43 @@ sub format_2822_time {
     usage();
 }
 
+# Now, let's fill any that aren't set in with defaults:
+
+sub read_config {
+       my ($prefix) = @_;
+
+       foreach my $setting (keys %config_bool_settings) {
+               my $target = $config_bool_settings{$setting}->[0];
+               $$target = $repo->config_bool("$prefix.$setting") unless (defined $$target);
+       }
+
+       foreach my $setting (keys %config_settings) {
+               my $target = $config_settings{$setting};
+               if (ref($target) eq "ARRAY") {
+                       unless (@$target) {
+                               my @values = $repo->config("$prefix.$setting");
+                               @$target = @values if (@values && defined $values[0]);
+                       }
+               }
+               else {
+                       $$target = $repo->config("$prefix.$setting") unless (defined $$target);
+               }
+       }
+}
+
+# read configuration from [sendemail "$identity"], fall back on [sendemail]
+$identity = $repo->config("sendemail.identity") unless (defined $identity);
+read_config("sendemail.$identity") if (defined $identity);
+read_config("sendemail");
+
+# fall back on builtin bool defaults
+foreach my $setting (values %config_bool_settings) {
+       ${$setting->[0]} = $setting->[1] unless (defined (${$setting->[0]}));
+}
+
+my ($repoauthor) = $repo->ident_person('author');
+my ($repocommitter) = $repo->ident_person('committer');
+
 # Verify the user input
 
 foreach my $entry (@to) {
@@ -222,14 +273,7 @@ sub format_2822_time {
        die "Comma in --bcclist entry: $entry'\n" unless $entry !~ m/,/;
 }
 
-# Now, let's fill any that aren't set in with defaults:
-
-my ($repoauthor) = $repo->ident_person('author');
-my ($repocommitter) = $repo->ident_person('committer');
-
 my %aliases;
-my @alias_files = $repo->config('sendemail.aliasesfile');
-my $aliasfiletype = $repo->config('sendemail.aliasfiletype');
 my %parse_alias = (
        # multiline formats can be supported in the future
        mutt => sub { my $fh = shift; while (<$fh>) {
@@ -321,10 +365,7 @@ sub expand_aliases {
        $initial_reply_to =~ s/>?\s+$/>/;
 }
 
-if (!$smtp_server) {
-       $smtp_server = $repo->config('sendemail.smtpserver');
-}
-if (!$smtp_server) {
+if (!defined $smtp_server) {
        foreach (qw( /usr/sbin/sendmail /usr/lib/sendmail )) {
                if (-x $_) {
                        $smtp_server = $_;
@@ -561,8 +602,16 @@ sub send_message
                print $sm "$header\n$message";
                close $sm or die $?;
        } else {
-               require Net::SMTP;
-               $smtp ||= Net::SMTP->new( $smtp_server );
+               if ($smtp_ssl) {
+                       require Net::SMTP::SSL;
+                       $smtp ||= Net::SMTP::SSL->new( $smtp_server, Port => 465 );
+               }
+               else {
+                       require Net::SMTP;
+                       $smtp ||= Net::SMTP->new( $smtp_server );
+               }
+               $smtp->auth( $smtp_authuser, $smtp_authpass )
+                       or die $smtp->message if (defined $smtp_authuser);
                $smtp->mail( $raw_from ) or die $smtp->message;
                $smtp->to( @recipients ) or die $smtp->message;
                $smtp->data or die $smtp->message;
@@ -669,7 +718,7 @@ sub send_message
        }
        close F;
 
-       if ($cc_cmd ne "") {
+       if (defined $cc_cmd) {
                open(F, "$cc_cmd $t |")
                        or die "(cc-cmd) Could not execute '$cc_cmd'";
                while(<F>) {
index 673aa27a452b11af62d89986bf21c8a4f10e0c41..727b1d3206da6964affe80d92699cd39c1878e34 100755 (executable)
@@ -39,6 +39,32 @@ get_repo_base() {
        ) 2>/dev/null
 }
 
+# Resolve relative url by appending to parent's url
+resolve_relative_url ()
+{
+       branch="$(git symbolic-ref HEAD 2>/dev/null)"
+       remote="$(git config branch.${branch#refs/heads/}.remote)"
+       remote="${remote:-origin}"
+       remoteurl="$(git config remote.$remote.url)" ||
+               die "remote ($remote) does not have a url in .git/config"
+       url="$1"
+       while test -n "$url"
+       do
+               case "$url" in
+               ../*)
+                       url="${url#../}"
+                       remoteurl="${remoteurl%/*}"
+                       ;;
+               ./*)
+                       url="${url#./}"
+                       ;;
+               *)
+                       break;;
+               esac
+       done
+       echo "$remoteurl/$url"
+}
+
 #
 # Map submodule path to submodule name
 #
@@ -103,11 +129,19 @@ module_add()
                usage
        fi
 
-       # Turn the source into an absolute path if
-       # it is local
-       if base=$(get_repo_base "$repo"); then
-               repo="$base"
-       fi
+       case "$repo" in
+       ./*|../*)
+               # dereference source url relative to parent's url
+               realrepo="$(resolve_relative_url $repo)" ;;
+       *)
+               # Turn the source into an absolute path if
+               # it is local
+               if base=$(get_repo_base "$repo"); then
+                       repo="$base"
+                       realrepo=$repo
+               fi
+               ;;
+       esac
 
        # Guess path from repo if not specified or strip trailing slashes
        if test -z "$path"; then
@@ -122,7 +156,7 @@ module_add()
        git ls-files --error-unmatch "$path" > /dev/null 2>&1 &&
        die "'$path' already exists in the index"
 
-       module_clone "$path" "$repo" || exit
+       module_clone "$path" "$realrepo" || exit
        (unset GIT_DIR && cd "$path" && git checkout -q ${branch:+-b "$branch" "origin/$branch"}) ||
        die "Unable to checkout submodule '$path'"
        git add "$path" ||
@@ -153,6 +187,13 @@ modules_init()
                test -z "$url" &&
                die "No url found for submodule path '$path' in .gitmodules"
 
+               # Possibly a url relative to parent
+               case "$url" in
+               ./*|../*)
+                       url="$(resolve_relative_url "$url")"
+                       ;;
+               esac
+
                git config submodule."$name".url "$url" ||
                die "Failed to register url for submodule path '$path'"
 
index c015ea8580e8e19dc27a0f66af38995335df8c82..484b0576a8cdea3b0d2063ce73c8b8cb11a18f7c 100755 (executable)
@@ -124,7 +124,8 @@ BEGIN
                        "Set an SVN repository to a git tree-ish",
                        { 'stdin|' => \$_stdin, %cmt_opts, %fc_opts, } ],
        'show-ignore' => [ \&cmd_show_ignore, "Show svn:ignore listings",
-                       { 'revision|r=i' => \$_revision } ],
+                       { 'revision|r=i' => \$_revision
+                       } ],
        'multi-fetch' => [ \&cmd_multi_fetch,
                           "Deprecated alias for $0 fetch --all",
                           { 'revision|r=s' => \$_revision, %fc_opts } ],
@@ -144,10 +145,10 @@ BEGIN
                          'non-recursive' => \$Git::SVN::Log::non_recursive,
                          'authors-file|A=s' => \$_authors,
                          'color' => \$Git::SVN::Log::color,
-                         'pager=s' => \$Git::SVN::Log::pager,
+                         'pager=s' => \$Git::SVN::Log::pager
                        } ],
        'find-rev' => [ \&cmd_find_rev, "Translate between SVN revision numbers and tree-ish",
-                       { } ],
+                       {} ],
        'rebase' => [ \&cmd_rebase, "Fetch and rebase your working directory",
                        { 'merge|m|M' => \$_merge,
                          'verbose|v' => \$_verbose,
@@ -811,7 +812,8 @@ sub cmt_metadata {
 
 sub working_head_info {
        my ($head, $refs) = @_;
-       my ($fh, $ctx) = command_output_pipe('log', '--no-color', $head);
+       my @args = ('log', '--no-color', '--first-parent');
+       my ($fh, $ctx) = command_output_pipe(@args, $head);
        my $hash;
        my %max;
        while (<$fh>) {
diff --git a/git.c b/git.c
index fd3d83cd4c49409214dcc3e54480b7650e1b8b18..56ae8ccccf6917fb27f74d56d7d96a9c4788cb54 100644 (file)
--- a/git.c
+++ b/git.c
@@ -364,6 +364,7 @@ static void handle_internal_command(int argc, const char **argv)
                { "reflog", cmd_reflog, RUN_SETUP },
                { "repo-config", cmd_config },
                { "rerere", cmd_rerere, RUN_SETUP },
+               { "reset", cmd_reset, RUN_SETUP },
                { "rev-list", cmd_rev_list, RUN_SETUP },
                { "rev-parse", cmd_rev_parse, RUN_SETUP },
                { "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
index b2bae1b250a5316c85ffb221b3b088c9e8cda8c7..3064298f28837ced72897b6bc6401b3061882926 100755 (executable)
@@ -2809,7 +2809,7 @@ sub git_difftree_body {
              "diff_tree\">\n";
 
        # header only for combined diff in 'commitdiff' view
-       my $has_header = @parents > 1 && $action eq 'commitdiff';
+       my $has_header = @$difftree && @parents > 1 && $action eq 'commitdiff';
        if ($has_header) {
                # table header
                print "<thead><tr>\n" .
@@ -3191,44 +3191,10 @@ sub git_patchset_body {
                                }
                        } until (!defined $to_name || $to_name eq $diffinfo->{'to_file'} ||
                                 $patch_idx > $#$difftree);
+
                        # modifies %from, %to hashes
                        parse_from_to_diffinfo($diffinfo, \%from, \%to, @hash_parents);
-                       if ($diffinfo->{'nparents'}) {
-                               # combined diff
-                               $from{'file'} = [];
-                               $from{'href'} = [];
-                               fill_from_file_info($diffinfo, @hash_parents)
-                                       unless exists $diffinfo->{'from_file'};
-                               for (my $i = 0; $i < $diffinfo->{'nparents'}; $i++) {
-                                       $from{'file'}[$i] = $diffinfo->{'from_file'}[$i] || $diffinfo->{'to_file'};
-                                       if ($diffinfo->{'status'}[$i] ne "A") { # not new (added) file
-                                               $from{'href'}[$i] = href(action=>"blob",
-                                                                        hash_base=>$hash_parents[$i],
-                                                                        hash=>$diffinfo->{'from_id'}[$i],
-                                                                        file_name=>$from{'file'}[$i]);
-                                       } else {
-                                               $from{'href'}[$i] = undef;
-                                       }
-                               }
-                       } else {
-                               $from{'file'} = $diffinfo->{'from_file'} || $diffinfo->{'file'};
-                               if ($diffinfo->{'status'} ne "A") { # not new (added) file
-                                       $from{'href'} = href(action=>"blob", hash_base=>$hash_parent,
-                                                            hash=>$diffinfo->{'from_id'},
-                                                            file_name=>$from{'file'});
-                               } else {
-                                       delete $from{'href'};
-                               }
-                       }
 
-                       $to{'file'} = $diffinfo->{'to_file'} || $diffinfo->{'file'};
-                       if (!is_deleted($diffinfo)) { # file exists in result
-                               $to{'href'} = href(action=>"blob", hash_base=>$hash,
-                                                  hash=>$diffinfo->{'to_id'},
-                                                  file_name=>$to{'file'});
-                       } else {
-                               delete $to{'href'};
-                       }
                        # this is first patch for raw difftree line with $patch_idx index
                        # we index @$difftree array from 0, but number patches from 1
                        print "<div class=\"patch\" id=\"patch". ($patch_idx+1) ."\">\n";
index 16f6a0f98b5df8fbb9bb5263d0d32afca678cbec..97dcf9bf02d8f382ba63cfeef9d6dcb792c62f76 100644 (file)
@@ -96,6 +96,7 @@ static struct path_list current_directory_set = {NULL, 0, 0, 1};
 
 static int call_depth = 0;
 static int verbosity = 2;
+static int rename_limit = -1;
 static int buffer_output = 1;
 static struct output_buffer *output_list, *output_end;
 
@@ -171,30 +172,6 @@ static void output_commit_title(struct commit *commit)
        }
 }
 
-static struct cache_entry *make_cache_entry(unsigned int mode,
-               const unsigned char *sha1, const char *path, int stage, int refresh)
-{
-       int size, len;
-       struct cache_entry *ce;
-
-       if (!verify_path(path))
-               return NULL;
-
-       len = strlen(path);
-       size = cache_entry_size(len);
-       ce = xcalloc(1, size);
-
-       hashcpy(ce->sha1, sha1);
-       memcpy(ce->name, path, len);
-       ce->ce_flags = create_ce_flags(len, stage);
-       ce->ce_mode = create_ce_mode(mode);
-
-       if (refresh)
-               return refresh_cache_entry(ce, 0);
-
-       return ce;
-}
-
 static int add_cacheinfo(unsigned int mode, const unsigned char *sha1,
                const char *path, int stage, int refresh, int options)
 {
@@ -396,6 +373,7 @@ static struct path_list *get_renames(struct tree *tree,
        diff_setup(&opts);
        opts.recursive = 1;
        opts.detect_rename = DIFF_DETECT_RENAME;
+       opts.rename_limit = rename_limit;
        opts.output_format = DIFF_FORMAT_NO_OUTPUT;
        if (diff_setup_done(&opts) < 0)
                die("diff setup failed");
@@ -1717,6 +1695,10 @@ static int merge_config(const char *var, const char *value)
                verbosity = git_config_int(var, value);
                return 0;
        }
+       if (!strcasecmp(var, "diff.renamelimit")) {
+               rename_limit = git_config_int(var, value);
+               return 0;
+       }
        return git_default_config(var, value);
 }
 
index 8b1c94e0e3f539cd0b507fd130b60aa443b9680b..2e40a344209e010e664758865846b63dc9546c1f 100644 (file)
@@ -346,6 +346,7 @@ int remove_file_from_index(struct index_state *istate, const char *path)
        int pos = index_name_pos(istate, path, strlen(path));
        if (pos < 0)
                pos = -pos-1;
+       cache_tree_invalidate_path(istate->cache_tree, path);
        while (pos < istate->cache_nr && !strcmp(istate->cache[pos]->name, path))
                remove_index_entry_at(istate, pos);
        return 0;
@@ -430,10 +431,34 @@ int add_file_to_index(struct index_state *istate, const char *path, int verbose)
                die("unable to add %s to index",path);
        if (verbose)
                printf("add '%s'\n", path);
-       cache_tree_invalidate_path(istate->cache_tree, path);
        return 0;
 }
 
+struct cache_entry *make_cache_entry(unsigned int mode,
+               const unsigned char *sha1, const char *path, int stage,
+               int refresh)
+{
+       int size, len;
+       struct cache_entry *ce;
+
+       if (!verify_path(path))
+               return NULL;
+
+       len = strlen(path);
+       size = cache_entry_size(len);
+       ce = xcalloc(1, size);
+
+       hashcpy(ce->sha1, sha1);
+       memcpy(ce->name, path, len);
+       ce->ce_flags = create_ce_flags(len, stage);
+       ce->ce_mode = create_ce_mode(mode);
+
+       if (refresh)
+               return refresh_cache_entry(ce, 0);
+
+       return ce;
+}
+
 int ce_same_name(struct cache_entry *a, struct cache_entry *b)
 {
        int len = ce_namelen(a);
@@ -673,6 +698,7 @@ static int add_index_entry_with_check(struct index_state *istate, struct cache_e
        int ok_to_replace = option & ADD_CACHE_OK_TO_REPLACE;
        int skip_df_check = option & ADD_CACHE_SKIP_DFCHECK;
 
+       cache_tree_invalidate_path(istate->cache_tree, ce->name);
        pos = index_name_pos(istate, ce->name, ntohs(ce->ce_flags));
 
        /* existing match? Just replace it. */
diff --git a/refs.c b/refs.c
index 09a2c87fc23e4298bb3bcddc5c2cc649c3206a04..7fb3350789a407e36dc74418b79508e9bf916594 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -1455,3 +1455,30 @@ int for_each_reflog(each_ref_fn fn, void *cb_data)
 {
        return do_for_each_reflog("", fn, cb_data);
 }
+
+int update_ref(const char *action, const char *refname,
+               const unsigned char *sha1, const unsigned char *oldval,
+               int flags, enum action_on_err onerr)
+{
+       static struct ref_lock *lock;
+       lock = lock_any_ref_for_update(refname, oldval, flags);
+       if (!lock) {
+               const char *str = "Cannot lock the ref '%s'.";
+               switch (onerr) {
+               case MSG_ON_ERR: error(str, refname); break;
+               case DIE_ON_ERR: die(str, refname); break;
+               case QUIET_ON_ERR: break;
+               }
+               return 1;
+       }
+       if (write_ref_sha1(lock, sha1, action) < 0) {
+               const char *str = "Cannot update the ref '%s'.";
+               switch (onerr) {
+               case MSG_ON_ERR: error(str, refname); break;
+               case DIE_ON_ERR: die(str, refname); break;
+               case QUIET_ON_ERR: break;
+               }
+               return 1;
+       }
+       return 0;
+}
diff --git a/refs.h b/refs.h
index f234eb76ba5d6aba03f484fe58d11ba81a90ff5a..6eb98a4caf150776c26fb04699133b1a976d0122 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -64,4 +64,10 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
 /** resolve ref in nested "gitlink" repository */
 extern int resolve_gitlink_ref(const char *name, const char *refname, unsigned char *result);
 
+/** lock a ref and then write its file */
+enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
+int update_ref(const char *action, const char *refname,
+               const unsigned char *sha1, const unsigned char *oldval,
+               int flags, enum action_on_err onerr);
+
 #endif /* REFS_H */
index 33d092c3c4b4c08fb6f39228e464219df4cb4f1c..658471385cfaa2480ee78b19fe38fc51d9b51ab2 100644 (file)
@@ -1209,8 +1209,6 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
 
                        opts = diff_opt_parse(&revs->diffopt, argv+i, argc-i);
                        if (opts > 0) {
-                               if (strcmp(argv[i], "-z"))
-                                       revs->diff = 1;
                                i += opts - 1;
                                continue;
                        }
@@ -1254,6 +1252,14 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch
                add_pending_object_with_mode(revs, object, def, mode);
        }
 
+       /* Did the user ask for any diff output? Run the diff! */
+       if (revs->diffopt.output_format & ~DIFF_FORMAT_NO_OUTPUT)
+               revs->diff = 1;
+
+       /* Pickaxe needs diffs */
+       if (revs->diffopt.pickaxe)
+               revs->diff = 1;
+
        if (revs->topo_order)
                revs->limited = 1;
 
index 9fc8a812f4c526d61e3acb8183b8b8da0e36895c..f74e66a8babd427ecd715c901d3d1680f1c5f13a 100644 (file)
@@ -307,20 +307,14 @@ static int send_pack(int in, int out, struct remote *remote, int nr_refspec, cha
                        rs.src = ref->name;
                        rs.dst = NULL;
                        if (!remote_find_tracking(remote, &rs)) {
-                               struct ref_lock *lock;
                                fprintf(stderr, " Also local %s\n", rs.dst);
                                if (will_delete_ref) {
                                        if (delete_ref(rs.dst, NULL)) {
                                                error("Failed to delete");
                                        }
-                               } else {
-                                       lock = lock_any_ref_for_update(rs.dst, NULL, 0);
-                                       if (!lock)
-                                               error("Failed to lock");
-                                       else
-                                               write_ref_sha1(lock, ref->new_sha1,
-                                                              "update by push");
-                               }
+                               } else
+                                       update_ref("update by push", rs.dst,
+                                               ref->new_sha1, NULL, 0, 0);
                                free(rs.dst);
                        }
                }
index 718c9c1fa31fa63fee65a7da857f0c9378af3c5c..1af73a47c6af80c51a6ac6df4c5b20529c1a3565 100755 (executable)
@@ -80,7 +80,7 @@ cat "$1".tmp
 action=pick
 for line in $FAKE_LINES; do
        case $line in
-       squash)
+       squash|edit)
                action="$line";;
        *)
                echo sed -n "${line}s/^pick/$action/p"
@@ -297,4 +297,16 @@ test_expect_success 'ignore patch if in upstream' '
        test $HEAD = $(git rev-parse HEAD^)
 '
 
+test_expect_success '--continue tries to commit, even for "edit"' '
+       parent=$(git rev-parse HEAD^) &&
+       test_tick &&
+       FAKE_LINES="edit 1" git rebase -i HEAD^ &&
+       echo edited > file7 &&
+       git add file7 &&
+       FAKE_COMMIT_MESSAGE="chouette!" git rebase --continue &&
+       test edited = $(git show HEAD:file7) &&
+       git show HEAD | grep chouette &&
+       test $parent = $(git rev-parse HEAD^)
+'
+
 test_done
index 1a4c53a031608a16785e6ac9a0531696156bacba..42e28ab758358500183ee8ade35d7052c69ce9e1 100755 (executable)
@@ -28,12 +28,15 @@ commit id embedding:
 TAR=${TAR:-tar}
 UNZIP=${UNZIP:-unzip}
 
+SUBSTFORMAT=%H%n
+
 test_expect_success \
     'populate workdir' \
     'mkdir a b c &&
      echo simple textfile >a/a &&
      mkdir a/bin &&
      cp /bin/sh a/bin &&
+     printf "A\$Format:%s\$O" "$SUBSTFORMAT" >a/substfile &&
      ln -s a a/l1 &&
      (p=long_path_to_a_file && cd a &&
       for depth in 1 2 3 4 5; do mkdir $p && cd $p; done &&
@@ -104,6 +107,22 @@ test_expect_success \
     'validate file contents with prefix' \
     'diff -r a c/prefix/a'
 
+test_expect_success \
+    'create an archive with a substfile' \
+    'echo substfile export-subst >a/.gitattributes &&
+     git archive HEAD >f.tar &&
+     rm a/.gitattributes'
+
+test_expect_success \
+    'extract substfile' \
+    '(mkdir f && cd f && $TAR xf -) <f.tar'
+
+test_expect_success \
+     'validate substfile contents' \
+     'git log --max-count=1 "--pretty=format:A${SUBSTFORMAT}O" HEAD \
+      >f/a/substfile.expected &&
+      diff f/a/substfile.expected f/a/substfile'
+
 test_expect_success \
     'git archive --format=zip' \
     'git archive --format=zip HEAD >d.zip'
diff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh
new file mode 100755 (executable)
index 0000000..1c4b0b3
--- /dev/null
@@ -0,0 +1,56 @@
+#!/bin/sh
+#
+# Copyright (c) 2006 Josh England
+#
+
+test_description='Test the post-merge hook.'
+. ./test-lib.sh
+
+test_expect_success setup '
+       echo Data for commit0. >a &&
+       git update-index --add a &&
+       tree0=$(git write-tree) &&
+       commit0=$(echo setup | git commit-tree $tree0) &&
+       echo Changed data for commit1. >a &&
+       git update-index a &&
+       tree1=$(git write-tree) &&
+       commit1=$(echo modify | git commit-tree $tree1 -p $commit0) &&
+        git update-ref refs/heads/master $commit0 &&
+       git-clone ./. clone1 &&
+       GIT_DIR=clone1/.git git update-index --add a &&
+       git-clone ./. clone2 &&
+       GIT_DIR=clone2/.git git update-index --add a
+'
+
+for clone in 1 2; do
+    cat >clone${clone}/.git/hooks/post-merge <<'EOF'
+#!/bin/sh
+echo $@ >> $GIT_DIR/post-merge.args
+EOF
+    chmod u+x clone${clone}/.git/hooks/post-merge
+done
+
+test_expect_failure 'post-merge does not run for up-to-date ' '
+        GIT_DIR=clone1/.git git merge $commit0 &&
+       test -e clone1/.git/post-merge.args
+'
+
+test_expect_success 'post-merge runs as expected ' '
+        GIT_DIR=clone1/.git git merge $commit1 &&
+       test -e clone1/.git/post-merge.args
+'
+
+test_expect_success 'post-merge from normal merge receives the right argument ' '
+        grep 0 clone1/.git/post-merge.args
+'
+
+test_expect_success 'post-merge from squash merge runs as expected ' '
+        GIT_DIR=clone2/.git git merge --squash $commit1 &&
+       test -e clone2/.git/post-merge.args
+'
+
+test_expect_success 'post-merge from squash merge receives the right argument ' '
+        grep 1 clone2/.git/post-merge.args
+'
+
+test_done
diff --git a/t/t5505-remote.sh b/t/t5505-remote.sh
new file mode 100755 (executable)
index 0000000..636aec2
--- /dev/null
@@ -0,0 +1,100 @@
+#!/bin/sh
+
+test_description='git remote porcelain-ish'
+
+. ./test-lib.sh
+
+GIT_CONFIG=.git/config
+export GIT_CONFIG
+
+setup_repository () {
+       mkdir "$1" && (
+       cd "$1" &&
+       git init &&
+       >file &&
+       git add file &&
+       git commit -m "Initial" &&
+       git checkout -b side &&
+       >elif &&
+       git add elif &&
+       git commit -m "Second" &&
+       git checkout master
+       )
+}
+
+tokens_match () {
+       echo "$1" | tr ' ' '\012' | sort | sed -e '/^$/d' >expect &&
+       echo "$2" | tr ' ' '\012' | sort | sed -e '/^$/d' >actual &&
+       diff -u expect actual
+}
+
+check_remote_track () {
+       actual=$(git remote show "$1" | sed -n -e '$p') &&
+       shift &&
+       tokens_match "$*" "$actual"
+}
+
+check_tracking_branch () {
+       f="" &&
+       r=$(git for-each-ref "--format=%(refname)" |
+               sed -ne "s|^refs/remotes/$1/||p") &&
+       shift &&
+       tokens_match "$*" "$r"
+}
+
+test_expect_success setup '
+
+       setup_repository one &&
+       setup_repository two &&
+       (
+               cd two && git branch another
+       ) &&
+       git clone one test
+
+'
+
+test_expect_success 'remote information for the origin' '
+(
+       cd test &&
+       tokens_match origin "$(git remote)" &&
+       check_remote_track origin master side &&
+       check_tracking_branch origin HEAD master side
+)
+'
+
+test_expect_success 'add another remote' '
+(
+       cd test &&
+       git remote add -f second ../two &&
+       tokens_match "origin second" "$(git remote)" &&
+       check_remote_track origin master side &&
+       check_remote_track second master side another &&
+       check_tracking_branch second master side another &&
+       git for-each-ref "--format=%(refname)" refs/remotes |
+       sed -e "/^refs\/remotes\/origin\//d" \
+           -e "/^refs\/remotes\/second\//d" >actual &&
+       >expect &&
+       diff -u expect actual
+)
+'
+
+test_expect_success 'remove remote' '
+(
+       cd test &&
+       git remote rm second
+)
+'
+
+test_expect_success 'remove remote' '
+(
+       cd test &&
+       tokens_match origin "$(git remote)" &&
+       check_remote_track origin master side &&
+       git for-each-ref "--format=%(refname)" refs/remotes |
+       sed -e "/^refs\/remotes\/origin\//d" >actual &&
+       >expect &&
+       diff -u expect actual
+)
+'
+
+test_done
diff --git a/t/t7102-reset.sh b/t/t7102-reset.sh
new file mode 100755 (executable)
index 0000000..f64b1cb
--- /dev/null
@@ -0,0 +1,405 @@
+#!/bin/sh
+#
+# Copyright (c) 2007 Carlos Rica
+#
+
+test_description='git-reset
+
+Documented tests for git-reset'
+
+. ./test-lib.sh
+
+test_expect_success 'creating initial files and commits' '
+       test_tick &&
+       echo "1st file" >first &&
+       git add first &&
+       git commit -m "create 1st file" &&
+
+       echo "2nd file" >second &&
+       git add second &&
+       git commit -m "create 2nd file" &&
+
+       echo "2nd line 1st file" >>first &&
+       git commit -a -m "modify 1st file" &&
+
+       git rm first &&
+       git mv second secondfile &&
+       git commit -a -m "remove 1st and rename 2nd" &&
+
+       echo "1st line 2nd file" >secondfile &&
+       echo "2nd line 2nd file" >>secondfile &&
+       git commit -a -m "modify 2nd file"
+'
+# git log --pretty=oneline # to see those SHA1 involved
+
+check_changes () {
+       test "$(git rev-parse HEAD)" = "$1" &&
+       git diff | git diff .diff_expect - &&
+       git diff --cached | git diff .cached_expect - &&
+       for FILE in *
+       do
+               echo $FILE':'
+               cat $FILE || return
+       done | git diff .cat_expect -
+}
+
+>.diff_expect
+>.cached_expect
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+EOF
+
+test_expect_success 'giving a non existing revision should fail' '
+       ! git reset aaaaaa &&
+       ! git reset --mixed aaaaaa &&
+       ! git reset --soft aaaaaa &&
+       ! git reset --hard aaaaaa &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+test_expect_success \
+       'giving paths with options different than --mixed should fail' '
+       ! git reset --soft -- first &&
+       ! git reset --hard -- first &&
+       ! git reset --soft HEAD^ -- first &&
+       ! git reset --hard HEAD^ -- first &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+test_expect_success 'giving unrecognized options should fail' '
+       ! git reset --other &&
+       ! git reset -o &&
+       ! git reset --mixed --other &&
+       ! git reset --mixed -o &&
+       ! git reset --soft --other &&
+       ! git reset --soft -o &&
+       ! git reset --hard --other &&
+       ! git reset --hard -o &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+test_expect_success \
+       'trying to do reset --soft with pending merge should fail' '
+       git branch branch1 &&
+       git branch branch2 &&
+
+       git checkout branch1 &&
+       echo "3rd line in branch1" >>secondfile &&
+       git commit -a -m "change in branch1" &&
+
+       git checkout branch2 &&
+       echo "3rd line in branch2" >>secondfile &&
+       git commit -a -m "change in branch2" &&
+
+       ! git merge branch1 &&
+       ! git reset --soft &&
+
+       printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile &&
+       git commit -a -m "the change in branch2" &&
+
+       git checkout master &&
+       git branch -D branch1 branch2 &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+test_expect_success \
+       'trying to do reset --soft with pending checkout merge should fail' '
+       git branch branch3 &&
+       git branch branch4 &&
+
+       git checkout branch3 &&
+       echo "3rd line in branch3" >>secondfile &&
+       git commit -a -m "line in branch3" &&
+
+       git checkout branch4 &&
+       echo "3rd line in branch4" >>secondfile &&
+
+       git checkout -m branch3 &&
+       ! git reset --soft &&
+
+       printf "1st line 2nd file\n2nd line 2nd file\n3rd line" >secondfile &&
+       git commit -a -m "the line in branch3" &&
+
+       git checkout master &&
+       git branch -D branch3 branch4 &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+test_expect_success \
+       'resetting to HEAD with no changes should succeed and do nothing' '
+       git reset --hard &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset --hard HEAD &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset --soft &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset --soft HEAD &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset --mixed &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset --mixed HEAD &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+       git reset HEAD &&
+               check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+>.diff_expect
+cat >.cached_expect <<EOF
+diff --git a/secondfile b/secondfile
+index 1bbba79..44c5b58 100644
+--- a/secondfile
++++ b/secondfile
+@@ -1 +1,2 @@
+-2nd file
++1st line 2nd file
++2nd line 2nd file
+EOF
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+EOF
+test_expect_success '--soft reset only should show changes in diff --cached' '
+       git reset --soft HEAD^ &&
+       check_changes d1a4bc3abce4829628ae2dcb0d60ef3d1a78b1c4 &&
+       test "$(git rev-parse ORIG_HEAD)" = \
+                       3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+>.diff_expect
+>.cached_expect
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+3rd line 2nd file
+EOF
+test_expect_success \
+       'changing files and redo the last commit should succeed' '
+       echo "3rd line 2nd file" >>secondfile &&
+       git commit -a -C ORIG_HEAD &&
+       check_changes 3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d &&
+       test "$(git rev-parse ORIG_HEAD)" = \
+                       3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+>.diff_expect
+>.cached_expect
+cat >.cat_expect <<EOF
+first:
+1st file
+2nd line 1st file
+second:
+2nd file
+EOF
+test_expect_success \
+       '--hard reset should change the files and undo commits permanently' '
+       git reset --hard HEAD~2 &&
+       check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
+       test "$(git rev-parse ORIG_HEAD)" = \
+                       3d3b7be011a58ca0c179ae45d94e6c83c0b0cd0d
+'
+
+>.diff_expect
+cat >.cached_expect <<EOF
+diff --git a/first b/first
+deleted file mode 100644
+index 8206c22..0000000
+--- a/first
++++ /dev/null
+@@ -1,2 +0,0 @@
+-1st file
+-2nd line 1st file
+diff --git a/second b/second
+deleted file mode 100644
+index 1bbba79..0000000
+--- a/second
++++ /dev/null
+@@ -1 +0,0 @@
+-2nd file
+diff --git a/secondfile b/secondfile
+new file mode 100644
+index 0000000..44c5b58
+--- /dev/null
++++ b/secondfile
+@@ -0,0 +1,2 @@
++1st line 2nd file
++2nd line 2nd file
+EOF
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+EOF
+test_expect_success \
+       'redoing changes adding them without commit them should succeed' '
+       git rm first &&
+       git mv second secondfile &&
+
+       echo "1st line 2nd file" >secondfile &&
+       echo "2nd line 2nd file" >>secondfile &&
+       git add secondfile &&
+       check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e
+'
+
+cat >.diff_expect <<EOF
+diff --git a/first b/first
+deleted file mode 100644
+index 8206c22..0000000
+--- a/first
++++ /dev/null
+@@ -1,2 +0,0 @@
+-1st file
+-2nd line 1st file
+diff --git a/second b/second
+deleted file mode 100644
+index 1bbba79..0000000
+--- a/second
++++ /dev/null
+@@ -1 +0,0 @@
+-2nd file
+EOF
+>.cached_expect
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+EOF
+test_expect_success '--mixed reset to HEAD should unadd the files' '
+       git reset &&
+       check_changes ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
+       test "$(git rev-parse ORIG_HEAD)" = \
+                       ddaefe00f1da16864591c61fdc7adb5d7cd6b74e
+'
+
+>.diff_expect
+>.cached_expect
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+EOF
+test_expect_success 'redoing the last two commits should succeed' '
+       git add secondfile &&
+       git reset --hard ddaefe00f1da16864591c61fdc7adb5d7cd6b74e &&
+
+       git rm first &&
+       git mv second secondfile &&
+       git commit -a -m "remove 1st and rename 2nd" &&
+
+       echo "1st line 2nd file" >secondfile &&
+       echo "2nd line 2nd file" >>secondfile &&
+       git commit -a -m "modify 2nd file" &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+>.diff_expect
+>.cached_expect
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+3rd line in branch2
+EOF
+test_expect_success '--hard reset to HEAD should clear a failed merge' '
+       git branch branch1 &&
+       git branch branch2 &&
+
+       git checkout branch1 &&
+       echo "3rd line in branch1" >>secondfile &&
+       git commit -a -m "change in branch1" &&
+
+       git checkout branch2 &&
+       echo "3rd line in branch2" >>secondfile &&
+       git commit -a -m "change in branch2" &&
+
+       ! git pull . branch1 &&
+       git reset --hard &&
+       check_changes 77abb337073fb4369a7ad69ff6f5ec0e4d6b54bb
+'
+
+>.diff_expect
+>.cached_expect
+cat >.cat_expect <<EOF
+secondfile:
+1st line 2nd file
+2nd line 2nd file
+EOF
+test_expect_success \
+       '--hard reset to ORIG_HEAD should clear a fast-forward merge' '
+       git reset --hard HEAD^ &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+
+       git pull . branch1 &&
+       git reset --hard ORIG_HEAD &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc &&
+
+       git checkout master &&
+       git branch -D branch1 branch2 &&
+       check_changes 3ec39651e7f44ea531a5de18a9fa791c0fd370fc
+'
+
+cat > expect << EOF
+diff --git a/file1 b/file1
+index d00491f..7ed6ff8 100644
+--- a/file1
++++ b/file1
+@@ -1 +1 @@
+-1
++5
+diff --git a/file2 b/file2
+deleted file mode 100644
+index 0cfbf08..0000000
+--- a/file2
++++ /dev/null
+@@ -1 +0,0 @@
+-2
+EOF
+cat > cached_expect << EOF
+diff --git a/file4 b/file4
+new file mode 100644
+index 0000000..b8626c4
+--- /dev/null
++++ b/file4
+@@ -0,0 +1 @@
++4
+EOF
+test_expect_success 'test --mixed <paths>' '
+       echo 1 > file1 &&
+       echo 2 > file2 &&
+       git add file1 file2 &&
+       test_tick &&
+       git commit -m files &&
+       git rm file2 &&
+       echo 3 > file3 &&
+       echo 4 > file4 &&
+       echo 5 > file1 &&
+       git add file1 file3 file4 &&
+       ! git reset HEAD -- file1 file2 file3 &&
+       git diff > output &&
+       git diff output expect &&
+       git diff --cached > output &&
+       git diff output cached_expect
+'
+
+test_expect_success 'test resetting the index at give paths' '
+
+       mkdir sub &&
+       >sub/file1 &&
+       >sub/file2 &&
+       git update-index --add sub/file1 sub/file2 &&
+       T=$(git write-tree) &&
+       ! git reset HEAD sub/file2 &&
+       U=$(git write-tree) &&
+       echo "$T" &&
+       echo "$U" &&
+       ! git diff-index --cached --exit-code "$T" &&
+       test "$T" != "$U"
+
+'
+
+test_done
index e9ea33c18d8e0ffa2612e52748bbab4bf13ef513..83f94702025276ffea4400490630e64c9eef068b 100755 (executable)
@@ -30,7 +30,7 @@ test_expect_success 'Extract patches' '
 '
 
 test_expect_success 'Send patches' '
-     git send-email -from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
+     git send-email --from="Example <nobody@example.com>" --to=nobody@example.com --smtp-server="$(pwd)/fake.sendmail" $patches 2>errors
 '
 
 cat >expected <<\EOF
index 622ea1c0df1cdfcbabcd9a884abe151c4d0dff53..5aac644223cf217255a0fdbbb1238b4125fd2e6e 100755 (executable)
@@ -140,6 +140,7 @@ test_expect_success 'test show-ignore' "
        cd test_wc &&
        mkdir -p deeply/nested/directory &&
        svn add deeply &&
+       svn up &&
        svn propset -R svn:ignore 'no-such-file*' .
        svn commit -m 'propset svn:ignore'
        cd .. &&
index d8f9cab35dcff89469f1974dcc7130e3d38f471e..7ba76309ac9e57f9e5379bf93ecac4e6a4e4ad96 100755 (executable)
@@ -19,8 +19,7 @@ test_expect_success 'initialize repo' "
        poke trunk/readme &&
        svn commit -m 'another commit' &&
        svn up &&
-       svn mv -m 'rename to thunk' trunk thunk &&
-       svn up &&
+       svn mv trunk thunk &&
        echo goodbye >> thunk/readme &&
        poke thunk/readme &&
        svn commit -m 'bye now' &&
@@ -52,8 +51,10 @@ test_expect_success 'init and fetch from one svn-remote' "
         "
 
 test_expect_success 'follow deleted parent' "
-        svn cp -m 'resurrecting trunk as junk' \
-               -r2 $svnrepo/trunk $svnrepo/junk &&
+        (svn cp -m 'resurrecting trunk as junk' \
+               $svnrepo/trunk@2 $svnrepo/junk ||
+         svn cp -m 'resurrecting trunk as junk' \
+               -r2 $svnrepo/trunk $svnrepo/junk) &&
         git config --add svn-remote.svn.fetch \
           junk:refs/remotes/svn/junk &&
         git-svn fetch -i svn/thunk &&