Merge refs/heads/master from .
authorJunio C Hamano <junkio@cox.net>
Tue, 30 Aug 2005 02:09:48 +0000 (19:09 -0700)
committerJunio C Hamano <junkio@cox.net>
Tue, 30 Aug 2005 02:09:48 +0000 (19:09 -0700)
22 files changed:
Documentation/git-apply-patch-script.txt [deleted file]
Documentation/git-applymbox.txt
Documentation/git-applypatch.txt
Documentation/git-bisect-script.txt
Documentation/git-cherry-pick-script.txt [new file with mode: 0644]
Documentation/git-mailinfo.txt
Documentation/git-prune-script.txt
Documentation/git-repack-script.txt
Documentation/git-revert-script.txt
Documentation/git-show-branch.txt
Documentation/git.txt
Documentation/howto/rebase-from-internal-branch.txt
Makefile
git-apply-patch-script [deleted file]
git-cherry
git-rebase-script
git-repack-script
git-revert-script
git-sh-setup-script
show-branch.c
tools/git-applymbox
tools/mailinfo.c
diff --git a/Documentation/git-apply-patch-script.txt b/Documentation/git-apply-patch-script.txt
deleted file mode 100644 (file)
index 808d3cd..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-git-apply-patch-script(1)
-=========================
-v0.99.4, May 2005
-
-NAME
-----
-git-apply-patch-script - Sample script to apply the diffs from git-diff-*
-
-
-SYNOPSIS
---------
-'git-apply-patch-script'
-
-DESCRIPTION
------------
-This is a sample script to be used via the 'GIT_EXTERNAL_DIFF'
-environment variable to apply the differences that the "git-diff-*"
-family of commands report to the current work tree.
-
-
-Author
-------
-Written by Junio C Hamano <junkio@cox.net>
-
-Documentation
---------------
-Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>.
-
-GIT
----
-Part of the link:git.html[git] suite
-
index acf0bf3d63df6742a236a29392962892fadb5841..3d813ec4cf67c039cd49b7e3e35941056e69b1a8 100644 (file)
@@ -3,26 +3,66 @@ git-applymbox(1)
 
 NAME
 ----
-git-applymbox - Some git command not yet documented.
+git-applymbox - Apply a series of patches in a mailbox
 
 
 SYNOPSIS
 --------
-'git-applymbox' [ --option ] <args>...
+'git-applymbox' [-u] [-k] [-q] ( -c .dotest/<num> | <mbox> ) [ <signoff> ]
 
 DESCRIPTION
 -----------
-Does something not yet documented.
+Splits mail messages in a mailbox into commit log message,
+authorship information and patches, and applies them to the
+current branch.
 
 
 OPTIONS
 -------
---option::
-       Some option not yet documented.
+-q::
+       Apply patches interactively.  The user will be given
+       opportunity to edit the log message and the patch before
+       attempting to apply patch in each e-mail message.
 
-<args>...::
-       Some argument not yet documented.
+-k::
+       Usually the program 'cleans up' the Subject: header line
+       to extract the title line for the commit log message,
+       among which (1) remove 'Re:' or 're:', (2) leading
+       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
+       then prepends "[PATCH] ".  This flag forbids this
+       munging, and is most useful when used to read back 'git
+       format-patch --mbox' output.
 
+-u::
+       By default, the commit log message, author name and
+       author email are taken from the e-mail without any
+       charset conversion, after minimally decoding MIME
+       transfer encoding.  This flag causes the resulting
+       commit to be encoded in utf-8 by transliterating them.
+       Note that the patch is always used as is without charset
+       conversion, even with this flag.
+
+-c .dotest/<num>::
+       When the patch contained in an e-mail does not cleanly
+       apply, the command exits with an error message. The
+       patch and extracted message are found in .dotest/, and
+       you could re-run 'git applymbox' with '-c .dotest/<num>'
+       flag to restart the process after inspecting and fixing
+       them.
+
+<mbox>::
+       The name of the file that contains the e-mail messages
+       with patches.  This file should be in the UNIX mailbox
+       format.  See 'SubmittingPatches' document to learn about
+       the formatting convention for e-mail submission.
+
+<signoff>::
+       The name of the file that contains your "Signed-off-by"
+       line.  See 'SubmittingPatches' document to learn what
+       "Signed-off-by" line means.  You can also just say
+       'yes', 'true', 'me', or 'please' to use an automatically
+       generated "Signed-off-by" line based on your committer
+       identity.
 
 Author
 ------
index 55facd2c7a4c8e0c8a142ba3bc3d64902276ba84..14ce53aade7e0549fed75eb89c453c6556a6eacb 100644 (file)
@@ -3,25 +3,33 @@ git-applypatch(1)
 
 NAME
 ----
-git-applypatch - Some git command not yet documented.
+git-applypatch - Apply one patch extracted from an e-mail.
 
 
 SYNOPSIS
 --------
-'git-applypatch' [ --option ] <args>...
+'git-applypatch' <msg> <patch> <info> [<signoff>]
 
 DESCRIPTION
 -----------
-Does something not yet documented.
+Takes three files <msg>, <patch>, and <info> prepared from an
+e-mail message by 'git-mailinfo', and creates a commit.  It is
+usually not necessary to use this command directly.
 
 
 OPTIONS
 -------
---option::
-       Some option not yet documented.
+<msg>::
+       Commit log message (sans the first line, which comes
+       from e-mail Subject stored in <info>).
 
-<args>...::
-       Some argument not yet documented.
+<patch>::
+       The patch to apply.
+
+<info>:
+       Author and subject information extracted from e-mail,
+       used on "author" line and as the first line of the
+       commit log message.
 
 
 Author
index 1f5e38626b60f369d4c622c946008d2e7c82f50b..1b0345199c8961e18592d81014402b584b138cba 100644 (file)
@@ -3,25 +3,69 @@ git-bisect-script(1)
 
 NAME
 ----
-git-bisect-script - Some git command not yet documented.
+git-bisect-script - Find the change that introduced a bug
 
 
 SYNOPSIS
 --------
-'git-bisect-script' [ --option ] <args>...
+'git bisect' start
+'git bisect' bad <rev>
+'git bisect' good <rev>
+'git bisect' reset [<branch>]
 
 DESCRIPTION
 -----------
-Does something not yet documented.
+This command uses 'git-rev-list --bisect' option to help drive
+the binary search process to find which change introduced a bug,
+given an old "good" commit object name and a later "bad" commit
+object name.
 
+The way you use it is:
 
-OPTIONS
--------
---option::
-       Some option not yet documented.
+------------------------------------------------
+git bisect start
+git bisect bad                 # Current version is bad
+git bisect good v2.6.13-rc2    # v2.6.13-rc2 was the last version
+                               # tested that was good
+------------------------------------------------
 
-<args>...::
-       Some argument not yet documented.
+When you give at least one bad and one good versions, it will
+bisect the revision tree and say something like:
+
+------------------------------------------------
+Bisecting: 675 revisions left to test after this
+------------------------------------------------
+
+and check out the state in the middle. Now, compile that kernel, and boot
+it. Now, let's say that this booted kernel works fine, then just do
+
+------------------------------------------------
+git bisect good                        # this one is good
+------------------------------------------------
+
+which will now say
+
+------------------------------------------------
+Bisecting: 337 revisions left to test after this
+------------------------------------------------
+
+and you continue along, compiling that one, testing it, and depending on
+whether it is good or bad, you say "git bisect good" or "git bisect bad",
+and ask for the next bisection.
+
+Until you have no more left, and you'll have been left with the first bad
+kernel rev in "refs/bisect/bad".
+
+Oh, and then after you want to reset to the original head, do a
+
+------------------------------------------------
+git bisect reset
+------------------------------------------------
+
+to get back to the master branch, instead of being in one of the bisection
+branches ("git bisect start" will do that for you too, actually: it will
+reset the bisection state, and before it does that it checks that you're
+not using some old bisection branch).
 
 
 Author
diff --git a/Documentation/git-cherry-pick-script.txt b/Documentation/git-cherry-pick-script.txt
new file mode 100644 (file)
index 0000000..ab9fb22
--- /dev/null
@@ -0,0 +1,57 @@
+git-cherry-pick-script(1)
+=========================
+v0.99.5 Aug 2005
+
+NAME
+----
+git-cherry-pick-script - Apply the change introduced by an existing commit.
+
+SYNOPSIS
+--------
+'git-cherry-pick-script' [-n] [-r] <commit>
+
+DESCRIPTION
+-----------
+Given one existing commit, apply the change the patch introduces, and record a
+new commit that records it.  This requires your working tree to be clean (no
+modifications from the HEAD commit).
+
+OPTIONS
+-------
+<commit>::
+       Commit to cherry-pick.
+
+-r::
+       Usuall the command appends which commit was
+       cherry-picked after the original commit message when
+       making a commit.  This option, '--replay', causes it to
+       use the original commit message intact.  This is useful
+       when you are reordering the patches in your private tree
+       before publishing, and is used by 'git rebase'.
+
+-n::
+       Usually the command automatically creates a commit with
+       a commit log message stating which commit was
+       cherry-picked.  This flag applies the change necessary
+       to cherry-pick the named commit to your working tree,
+       but does not make the commit.  In addition, when this
+       option is used, your working tree does not have to match
+       the HEAD commit.  The cherry-pick is done against the
+       beginning state of your working tree.
+
+       This is useful when cherry-picking more than one commits'
+       effect to your working tree in a row.
+
+
+Author
+------
+Written by Junio C Hamano <junkio@cox.net>
+
+Documentation
+--------------
+Documentation by Junio C Hamano and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the link:git.html[git] suite
+
index b75f61c84ce07946cad2344502e0a823575d8c9c..eb3a3d1be7e6d45686408882ae16c5608faa8476 100644 (file)
@@ -3,30 +3,57 @@ git-mailinfo(1)
 
 NAME
 ----
-git-mailinfo - Some git command not yet documented.
+git-mailinfo - Extracts patch from a single e-mail message.
 
 
 SYNOPSIS
 --------
-'git-mailinfo' [ --option ] <args>...
+'git-mailinfo' [-k] [-u] <msg> <patch>
+
 
 DESCRIPTION
 -----------
-Does something not yet documented.
+Reading a single e-mail message from the standard input, and
+writes the commit log message in <msg> file, and the patches in
+<patch> file.  The author name, e-mail and e-mail subject are
+written out to the standard output to be used by git-applypatch
+to create a commit.  It is usually not necessary to use this
+command directly.
 
 
 OPTIONS
 -------
---option::
-       Some option not yet documented.
+-k::
+       Usually the program 'cleans up' the Subject: header line
+       to extract the title line for the commit log message,
+       among which (1) remove 'Re:' or 're:', (2) leading
+       whitespaces, (3) '[' up to ']', typically '[PATCH]', and
+       then prepends "[PATCH] ".  This flag forbids this
+       munging, and is most useful when used to read back 'git
+       format-patch --mbox' output.
+
+-u::
+       By default, the commit log message, author name and
+       author email are taken from the e-mail without any
+       charset conversion, after minimally decoding MIME
+       transfer encoding.  This flag causes the resulting
+       commit to be encoded in utf-8 by transliterating them.
+       Note that the patch is always used as is without charset
+       conversion, even with this flag.
 
-<args>...::
-       Some argument not yet documented.
+<msg>::
+       The commit log message extracted from e-mail, usually
+       except the title line which comes from e-mail Subject.
+
+<patch>::
+       The patch extracted from e-mail.
 
 
 Author
 ------
-Written by Linus Torvalds <torvalds@osdl.org>
+Written by Linus Torvalds <torvalds@osdl.org> and
+Junio C Hamano <junkio@cox.net>
+
 
 Documentation
 --------------
index e8305a6f3d7a7ef0972ca3142a14157c17fba1af..0f76e8fd0d3c4a3413e49710cf7f82448f10eec2 100644 (file)
@@ -1,6 +1,6 @@
 git-prune-script(1)
 ===================
-v0.1, May 2005
+v0.99.5, Aug 2005
 
 NAME
 ----
@@ -9,14 +9,24 @@ git-prune-script - Prunes all unreachable objects from the object database
 
 SYNOPSIS
 --------
-'git-prune-script'
+'git-prune-script' [-n]
 
 DESCRIPTION
 -----------
-This runs "git-fsck-cache --unreachable" program using the heads specified
-on the command line (or `$GIT_DIR/refs/heads/\*` and `$GIT_DIR/refs/tags/\*`
-if none is specified), and prunes all unreachable objects from the object
-database.
+
+This runs `git-fsck-cache --unreachable` using the heads
+specified on the command line (or `$GIT_DIR/refs/heads/\*` and
+`$GIT_DIR/refs/tags/\*` if none is specified), and prunes all
+unreachable objects from the object database.  In addition, it
+prunes the unpacked objects that are also found in packs by
+running `git prune-packed`.
+
+OPTIONS
+-------
+
+-n::
+       Do not remove anything; just report what it would
+       remove.
 
 
 Author
index 497d0dfe80738154b643e327f718977d42e2981a..cd449bcc2d751d6064c0e82e34217cb472a91d15 100644 (file)
@@ -1,6 +1,6 @@
 git-repack-script(1)
 =====================
-v0.1, August 2005
+v0.99.5, August 2005
 
 NAME
 ----
@@ -10,17 +10,36 @@ objects into pack files.
 
 SYNOPSIS
 --------
-'git-repack-script'
+'git-repack-script' [-a] [-d]
 
 DESCRIPTION
 -----------
-This script is used to combine all objects that do not currently reside in a
-"pack", into a pack.
 
-A pack is a collection of objects, individually compressed, with delta
-compression applied, stored in a single file, with an associated index file.
+This script is used to combine all objects that do not currently
+reside in a "pack", into a pack.
+
+A pack is a collection of objects, individually compressed, with
+delta compression applied, stored in a single file, with an
+associated index file.
+
+Packs are used to reduce the load on mirror systems, backup
+engines, disk storage, etc.
+
+OPTIONS
+-------
+
+-a::
+       Instead of incrementally packing the unpacked objects,
+       pack everything available into a single pack.
+       Especially useful when packing a repository that is used
+       for a private development and there no need to worry
+       about people fetching via dumb protocols from it.  Use
+       with '-d'.
+
+-d::
+       After packing, if the newly created packs make some
+       existing packs redundant, remove the redundant packs.
 
-Packs are used to reduce the load on mirror systems, backup engines, disk storage, etc.
 
 Author
 ------
index 727073e27958cfeaff10c0a4db2fb77b5f2ae73c..9609dcd325062d88871bf91b9642bef61f8f9224 100644 (file)
@@ -7,7 +7,7 @@ git-revert-script - Revert an existing commit.
 
 SYNOPSIS
 --------
-'git-revert-script' <commit>
+'git-revert-script' [-n] <commit>
 
 DESCRIPTION
 -----------
@@ -20,6 +20,20 @@ OPTIONS
 <commit>::
        Commit to revert.
 
+-n::
+       Usually the command automatically creates a commit with
+       a commit log message stating which commit was reverted.
+       This flag applies the change necessary to revert the
+       named commit to your working tree, but does not make the
+       commit.  In addition, when this option is used, your
+       working tree does not have to match the HEAD commit.
+       The revert is done against the beginning state of your
+       working tree.
+
+       This is useful when reverting more than one commits'
+       effect to your working tree in a row.
+
+
 Author
 ------
 Written by Junio C Hamano <junkio@cox.net>
index 71d5ba8fd78dfbbdefde52f3b1bae73a0e52e50b..5103c0c4f909a787e52c26cee4119212f1eb6c19 100644 (file)
@@ -1,6 +1,6 @@
 git-show-branch(1)
 ==================
-v0.99.4, Aug 2005
+v0.99.5, Aug 2005
 
 NAME
 ----
@@ -28,7 +28,8 @@ OPTIONS
 --more=<n>::
        Usually the command stops output upon showing the commit
        that is the common ancestor of all the branches.  This
-       flag tells the command to go <n> commits beyond that.
+       flag tells the command to go <n> more common commits
+       beyond that.
 
 --merge-base::
        Instead of showing the commit list, just act like the
index f63cbdd3727237b02c5be5b6f164dcae1fefd2f6..befe3e52cd8e0bfa5cbcd10005f42b816006af1e 100644 (file)
@@ -52,49 +52,50 @@ SCMs layered over git.
 
 Manipulation commands
 ~~~~~~~~~~~~~~~~~~~~~
+link:git-apply.html[git-apply]::
+       Reads a "diff -up1" or git generated patch file and
+       applies it to the working tree.
+
 link:git-checkout-cache.html[git-checkout-cache]::
        Copy files from the cache to the working directory
 
 link:git-commit-tree.html[git-commit-tree]::
        Creates a new commit object
 
+link:git-hash-object.html[git-hash-object]::
+       Computes the object ID from a file.
+
 link:git-init-db.html[git-init-db]::
        Creates an empty git object database
 
-link:git-merge-base.html[git-merge-base]::
-       Finds as good a common ancestor as possible for a merge
+link:git-merge-cache.html[git-merge-cache]::
+       Runs a merge for files needing merging
 
 link:git-mktag.html[git-mktag]::
        Creates a tag object
 
+link:git-pack-objects.html[git-pack-objects]::
+       Creates a packed archive of objects.
+
+link:git-prune-packed.html[git-prune-packed]::
+       Remove extra objects that are already in pack files.
+
 link:git-read-tree.html[git-read-tree]::
        Reads tree information into the directory cache
 
+link:git-unpack-objects.html[git-unpack-objects]::
+       Unpacks objects out of a packed archive.
+
 link:git-update-cache.html[git-update-cache]::
        Modifies the index or directory cache
 
-link:git-hash-object.html[git-hash-object]::
-       Computes the object ID from a file.
-
 link:git-write-tree.html[git-write-tree]::
        Creates a tree from the current cache
 
-link:git-pack-objects.html[git-pack-objects]::
-       Creates a packed archive of objects.
-
-link:git-unpack-objects.html[git-unpack-objects]::
-       Unpacks objects out of a packed archive.
-
-link:git-prune-packed.html[git-prune-packed]::
-       Remove extra objects that are already in pack files.
-
-link:git-apply.html[git-apply]::
-       Reads a "diff -up1" or git generated patch file and
-       applies it to the working tree.
-
 
 Interrogation commands
 ~~~~~~~~~~~~~~~~~~~~~~
+
 link:git-cat-file.html[git-cat-file]::
        Provide content or type information for repository objects
 
@@ -104,12 +105,12 @@ link:git-diff-cache.html[git-diff-cache]::
 link:git-diff-files.html[git-diff-files]::
        Compares files in the working tree and the cache
 
-link:git-diff-tree.html[git-diff-tree]::
-       Compares the content and mode of blobs found via two tree objects
-
 link:git-diff-stages.html[git-diff-stages]::
        Compares two "merge stages" in the index file.
 
+link:git-diff-tree.html[git-diff-tree]::
+       Compares the content and mode of blobs found via two tree objects
+
 link:git-export.html[git-export]::
        Exports each commit and a diff against each of its parents
 
@@ -122,8 +123,8 @@ link:git-ls-files.html[git-ls-files]::
 link:git-ls-tree.html[git-ls-tree]::
        Displays a tree object in human readable form
 
-link:git-merge-cache.html[git-merge-cache]::
-       Runs a merge for files needing merging
+link:git-merge-base.html[git-merge-base]::
+       Finds as good a common ancestor as possible for a merge
 
 link:git-rev-list.html[git-rev-list]::
        Lists commit objects in reverse chronological order
@@ -131,6 +132,9 @@ link:git-rev-list.html[git-rev-list]::
 link:git-rev-tree.html[git-rev-tree]::
        Provides the revision tree for one or more commits
 
+link:git-show-index.html[git-show-index]::
+       Displays contents of a pack idx file.
+
 link:git-tar-tree.html[git-tar-tree]::
        Creates a tar archive of the files in the named tree
 
@@ -140,9 +144,6 @@ link:git-unpack-file.html[git-unpack-file]::
 link:git-var.html[git-var]::
        Displays a git logical variable
 
-link:git-show-index.html[git-show-index]::
-       Displays contents of a pack idx file.
-
 link:git-verify-pack.html[git-verify-pack]::
        Validates packed GIT archive files
 
@@ -153,174 +154,172 @@ touch the working file set - but in general they don't
 Synching repositories
 ~~~~~~~~~~~~~~~~~~~~~
 
-link:git-clone-script.html[git-clone-script]::
-       Clones a repository into the current repository (user interface)
-
 link:git-clone-pack.html[git-clone-pack]::
        Clones a repository into the current repository (engine
        for ssh and local transport)
 
+link:git-fetch-pack.html[git-fetch-pack]::
+       Updates from a remote repository.
+
 link:git-http-pull.html[git-http-pull]::
        Downloads a remote GIT repository via HTTP
 
 link:git-local-pull.html[git-local-pull]::
        Duplicates another GIT repository on a local system
 
-link:git-ssh-pull.html[git-ssh-pull]::
-       Pulls from a remote repository over ssh connection
-
-link:git-send-pack.html[git-send-pack]::
-       Pushes to a remote repository, intelligently.
+link:git-peek-remote.html[git-peek-remote]::
+       Lists references on a remote repository using upload-pack protocol.
 
 link:git-receive-pack.html[git-receive-pack]::
        Invoked by 'git-send-pack' to receive what is pushed to it.
 
-link:git-clone-pack.html[git-clone-pack]::
-       Clones from a remote repository.
-
-link:git-fetch-pack.html[git-fetch-pack]::
-       Updates from a remote repository.
+link:git-send-pack.html[git-send-pack]::
+       Pushes to a remote repository, intelligently.
 
-link:git-peek-remote.html[git-peek-remote]::
-       Lists references on a remote repository using upload-pack protocol.
+link:git-ssh-pull.html[git-ssh-pull]::
+       Pulls from a remote repository over ssh connection
 
-link:git-upload-pack.html[git-upload-pack]::
-       Invoked by 'git-clone-pack' and 'git-fetch-pack' to push
-       what are asked for.
+link:git-ssh-push.html[git-ssh-push]::
+       Helper "server-side" program used by git-ssh-pull
 
 link:git-update-server-info.html[git-update-server-info]::
        Updates auxiliary information on a dumb server to help
        clients discover references and packs on it.
 
+link:git-upload-pack.html[git-upload-pack]::
+       Invoked by 'git-clone-pack' and 'git-fetch-pack' to push
+       what are asked for.
+
 
 Porcelain-ish Commands
 ----------------------
-link:git-revert-script.html[git-revert-script]::
-       Revert an existing commit.
-
-link:git-rebase-script.html[git-rebase-script]::
-       Rebase local commits to new upstream head.
 
 link:git-add-script.html[git-add-script]::
        Add paths to the index file.
 
+link:git-applymbox.html[git-applymbox]::
+       Apply patches from a mailbox.
+
+link:git-bisect-script.html[git-bisect-script]::
+       Find the change that introduced a bug.
+
 link:git-branch-script.html[git-branch-script]::
        Create and Show branches.
 
-link:git-whatchanged.html[git-whatchanged]::
-       Shows commit logs and differences they introduce.
-
-link:git-log-script.html[git-log-script]::
-       Shows commit logs.
+link:git-cherry-pick-script.html[git-cherry-pick-script]::
+       Cherry-pick the effect of an existing commit.
 
-link:git-shortlog.html[git-shortlog]::
-       Summarizes 'git log' output.
+link:git-clone-script.html[git-clone-script]::
+       Clones a repository into a new directory.
 
-link:git-status-script.html[git-status-script]::
-       Shows the working tree status.
+link:git-commit-script.html[git-commit-script]::
+       Record changes to the repository.
 
 link:git-fetch-script.html[git-fetch-script]::
        Download from a remote repository via various protocols.
 
-link:git-pull-script.html[git-pull-script]::
-       Fetch from and merge with a remote repository.
+link:git-log-script.html[git-log-script]::
+       Shows commit logs.
 
-link:git-resolve-script.html[git-resolve-script]::
-       Merge two commits.
+link:git-ls-remote-script.html[git-ls-remote-script]::
+       Shows references in a remote or local repository.
 
 link:git-octopus-script.html[git-octopus-script]::
        Merge more than two commits.
 
+link:git-pull-script.html[git-pull-script]::
+       Fetch from and merge with a remote repository.
+
 link:git-push-script.html[git-push-script]::
        Update remote refs along with associated objects.
 
-link:git-commit-script.html[git-commit-script]::
-       Record changes to the repository.
+link:git-rebase-script.html[git-rebase-script]::
+       Rebase local commits to new upstream head.
 
-link:git-show-branch.html[git-show-branch]::
-       Show branches and their commits.
+link:git-rename-script.html[git-rename]::
+       Rename files and directories.
 
 link:git-repack-script.html[git-repack-script]::
        Pack unpacked objects in a repository.
 
-link:git-rename-script.html[git-rename]::
-       Rename files and directories.
+link:git-resolve-script.html[git-resolve-script]::
+       Merge two commits.
 
-link:git-ls-remote-script.html[git-ls-remote-script]::
-       Shows references in a remote or local repository.
+link:git-revert-script.html[git-revert-script]::
+       Revert an existing commit.
+
+link:git-shortlog.html[git-shortlog]::
+       Summarizes 'git log' output.
+
+link:git-show-branch.html[git-show-branch]::
+       Show branches and their commits.
+
+link:git-status-script.html[git-status-script]::
+       Shows the working tree status.
 
 link:git-verify-tag-script.html[git-verify-tag-script]::
        Check the GPG signature of tag.
 
+link:git-whatchanged.html[git-whatchanged]::
+       Shows commit logs and differences they introduce.
+
 
 Ancillary Commands
 ------------------
 Manipulators:
 
-link:git-relink-script.html[git-relink-script]::
-       Hardlink common objects in local repositories.
-
-link:git-apply-patch-script.html[git-apply-patch-script]::
-       Sample script to apply the diffs from git-diff-*
+link:git-applypatch.html[git-applypatch]::
+       Apply one patch extracted from an e-mail.
 
 link:git-convert-cache.html[git-convert-cache]::
        Converts old-style GIT repository
 
+link:git-cvsimport-script.html[git-cvsimport-script]::
+       Salvage your data out of another SCM people love to hate.
+
 link:git-merge-one-file-script.html[git-merge-one-file-script]::
        The standard helper program to use with "git-merge-cache"
 
 link:git-prune-script.html[git-prune-script]::
        Prunes all unreachable objects from the object database
 
+link:git-relink-script.html[git-relink-script]::
+       Hardlink common objects in local repositories.
+
+link:git-sh-setup-script.html[git-sh-setup-script]::
+       Common git shell script setup code.
+
 link:git-tag-script.html[git-tag-script]::
        An example script to create a tag object signed with GPG
 
-link:git-cvsimport-script.html[git-cvsimport-script]::
-       Salvage your data out of another SCM people love to hate.
-
 
 Interrogators:
 
-link:git-patch-id.html[git-patch-id]::
-       Compute unique ID for a patch.
+link:git-cherry.html[git-cherry]::
+       Find commits not merged upstream.
 
 link:git-count-objects-script.html[git-count-objects-script]::
        Count unpacked number of objects and their disk consumption.
 
-link:git-cherry.html[git-cherry]::
-       Find commits not merged upstream.
-
 link:git-diff-helper.html[git-diff-helper]::
        Generates patch format output for git-diff-*
 
-link:git-ssh-push.html[git-ssh-push]::
-       Helper "server-side" program used by git-ssh-pull
+link:git-mailinfo.html[git-mailinfo]::
+       Extracts patch from a single e-mail message.
+
+link:git-mailsplit.html[git-mailsplit]::
+       git-mailsplit.
+
+link:git-patch-id.html[git-patch-id]::
+       Compute unique ID for a patch.
 
 link:git-send-email-script.html[git-send-email]::
        Send patch e-mails out of "format-patch --mbox" output.
 
-link:git-sh-setup-script.html[git-sh-setup-script]::
-       Common git shell script setup code.
-
 
 Commands not yet documented
 ---------------------------
 
-link:git-applymbox.html[git-applymbox]::
-       git-applymbox.
-
-link:git-applypatch.html[git-applypatch]::
-       git-applypatch.
-
-link:git-mailinfo.html[git-mailinfo]::
-       git-mailinfo.
-
-link:git-mailsplit.html[git-mailsplit]::
-       git-mailsplit.
-
-link:git-bisect-script.html[git-bisect-script]::
-       git-bisect-script.
-
 link:git-build-rev-cache.html[git-build-rev-cache]::
        git-build-rev-cache.
 
index f627e4271c40fc2c4b28f825828e205b2fc8cb88..4523b69d4fc018a67835e0751f08c9be17635d65 100644 (file)
@@ -38,7 +38,7 @@ ancestry graph looked like this:
 So I started from master, made a bunch of edits, and committed:
 
     $ git checkout master
-    $ cd Documentation; ed git.txt git-apply-patch-script.txt ...
+    $ cd Documentation; ed git.txt ...
     $ cd ..; git add Documentation/*.txt
     $ git commit -s -v
 
index 57d4f1170c06bfff42cb64a753741fd4fa6fa37c..f13ff5aa29ef9ade4d9652c970b79053b47d2d0d 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -57,7 +57,7 @@ SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
 
 
 
-SCRIPTS=git git-apply-patch-script git-merge-one-file-script git-prune-script \
+SCRIPTS=git git-merge-one-file-script git-prune-script \
        git-pull-script git-tag-script git-resolve-script git-whatchanged \
        git-fetch-script git-status-script git-commit-script \
        git-log-script git-shortlog git-cvsimport-script git-diff-script \
@@ -215,6 +215,7 @@ check:
 install: $(PROG) $(SCRIPTS)
        $(INSTALL) -m755 -d $(DESTDIR)$(bindir)
        $(INSTALL) $(PROG) $(SCRIPTS) $(DESTDIR)$(bindir)
+       $(INSTALL) git-revert-script $(DESTDIR)$(bindir)/git-cherry-pick-script
        $(MAKE) -C templates install
        $(MAKE) -C tools install
 
diff --git a/git-apply-patch-script b/git-apply-patch-script
deleted file mode 100755 (executable)
index 6261fd8..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-#!/bin/sh
-# Copyright (C) 2005 Junio C Hamano
-#
-# Applying diff between two trees to the work tree can be
-# done with the following single command:
-#
-# GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p $tree1 $tree2
-#
-
-case "$#" in
-1)
-    echo >&2 "cannot handle unmerged diff on path $1."
-    exit 1 ;;
-8 | 9)
-    echo >&2 "cannot handle rename diff between $1 and $8 yet."
-    exit 1 ;;
-esac
-name="$1" tmp1="$2" hex1="$3" mode1="$4" tmp2="$5" hex2="$6" mode2="$7"
-
-type1=f
-case "$mode1" in
-*120???) type1=l  ;;
-*1007??) mode1=+x ;;
-*1006??) mode1=-x ;;
-.)       type1=-  ;; 
-esac
-
-type2=f
-case "$mode2" in
-*120???) type2=l  ;;
-*1007??) mode2=+x ;;
-*1006??) mode2=-x ;;
-.)       type2=-  ;; 
-esac
-
-case "$type1,$type2" in
-
--,?)
-    dir=$(dirname "$name")
-    case "$dir" in '' | .) ;; *) mkdir -p "$dir" ;; esac || {
-       echo >&2 "cannot create leading path for $name."
-       exit 1
-    }
-    if test -e "$name"
-    then
-       echo >&2 "path $name to be created already exists."
-       exit 1
-    fi
-    case "$type2" in
-    f)
-        # creating a regular file
-       cat "$tmp2" >"$name" || {
-           echo >&2 "cannot create a regular file $name."
-           exit 1
-       } 
-       case "$mode2" in
-       +x)
-           echo >&2 "created a regular file $name with mode +x."
-           chmod "$mode2" "$name"
-           ;;
-       -x)
-           echo >&2 "created a regular file $name."
-            ;;
-        esac
-       ;;
-    l)
-        # creating a symlink
-        ln -s "$(cat "$tmp2")" "$name" || {
-           echo >&2 "cannot create a symbolic link $name."
-           exit 1
-       }
-       echo >&2 "created a symbolic link $name."
-        ;;
-    *)
-        echo >&2 "do not know how to create $name of type $type2."
-       exit 1
-    esac
-    git-update-cache --add -- "$name" ;;
-
-?,-)
-    rm -f "$name" || {
-       echo >&2 "cannot remove $name"
-       exit 1
-    }
-    echo >&2 "deleted $name."
-    git-update-cache --remove -- "$name" ;;
-
-l,f|f,l)
-    echo >&2 "cannot change a regular file $name and a symbolic link $name."
-    exit 1 ;;
-
-l,l)
-    # symlink to symlink
-    current=$(readlink "$name") || {
-       echo >&2 "cannot read the target of the symbolic link $name."
-       exit 1
-    }
-    original=$(cat "$tmp1")
-    next=$(cat "$tmp2")
-    test "$original" != "$current" || {
-       echo >&2 "cannot apply symbolic link target change ($original->$next) to $name which points to $current."
-       exit 1
-    }
-    if test "$next" != "$current"
-    then
-       rm -f "$name" && ln -s "$next" "$name" || {
-           echo >&2 "cannot create symbolic link $name."
-           exit 1
-       }
-       echo >&2 "changed symbolic target of $name."
-        git-update-cache -- "$name"
-    fi ;;
-
-f,f)
-    # changed
-    test -e "$name" || {
-       echo >&2 "regular file $name to be patched does not exist."
-       exit 1
-    }
-    dir=$(dirname "$name")
-    case "$dir" in '' | .) ;; *) mkdir -p "$dir";; esac || {
-       echo >&2 "cannot create leading path for $name."
-       exit 1
-    }
-    tmp=.git-apply-patch-$$
-    trap "rm -f $tmp-*" 0 1 2 3 15
-
-    # Be careful, in case "$tmp2" is borrowed path from the work tree
-    # we are looking at...
-    diff -u -L "a/$name" -L "b/$name" "$tmp1" "$tmp2" >$tmp-patch
-
-    # This will say "patching ..." so we do not say anything outselves.
-    patch -p1 <$tmp-patch || exit
-    rm -f $tmp-patch
-    case "$mode1,$mode2" in
-    "$mode2,$mode1") ;;
-    *)
-       chmod "$mode2" "$name"
-       echo >&2 "changed mode from $mode1 to $mode2."
-       ;;
-    esac
-    git-update-cache -- "$name"
-
-esac
index e186363647e2dcf0ecd72efb3cd609dde6a672bf..fe8c1090e97faa0fd0a34c2a12d748916aaa6b05 100755 (executable)
@@ -14,19 +14,9 @@ usage="usage: $0 "'[-v] <upstream> [<head>]
 
 Each commit between the fork-point and <head> is examined, and
 compared against the change each commit between the fork-point and
-<upstream> introduces.  If the change does not seem to be in the
-upstream, it is shown on the standard output.
-
-The output is intended to be used as:
-
-    OLD_HEAD=$(git-rev-parse HEAD)
-    git-rev-parse upstream >${GIT_DIR-.}/HEAD
-    git-cherry upstream $OLD_HEAD |
-    while read commit
-    do
-        GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p "$commit" &&
-       git-commit-script -C "$commit"
-    done
+<upstream> introduces.  If the change seems to be in the upstream,
+it is shown on the standard output with prefix "+".  Otherwise
+it is shown with prefix "-".
 '
 
 case "$1" in -v) verbose=t; shift ;; esac 
index a335b991782e56b9ce33520a4671539791ff4fb8..b0893cc10faa8e4ed0c9d7de815371034ef2b0a9 100755 (executable)
@@ -37,25 +37,32 @@ git-rev-parse --verify "$upstream^0" >"$GIT_DIR/HEAD" || exit
 
 tmp=.rebase-tmp$$
 fail=$tmp-fail
-trap "rm -rf $tmp-*" 1 2 3 15
+trap "rm -rf $tmp-*" 1 2 3 15
 
 >$fail
 
-git-cherry $upstream $ours |
-while read sign commit
+git-cherry -v $upstream $ours |
+while read sign commit msg
 do
        case "$sign" in
-       -) continue ;;
+       -)
+               echo >&2 "* Already applied: $msg"
+               continue ;;
        esac
+       echo >&2 "* Applying: $msg"
        S=`cat "$GIT_DIR/HEAD"` &&
-        GIT_EXTERNAL_DIFF=git-apply-patch-script git-diff-tree -p $commit &&
-       git-commit-script -C "$commit" || {
+       git-cherry-pick-script --replay $commit || {
+               echo >&2 "* Not applying the patch and continuing."
                echo $commit >>$fail
-               git-read-tree --reset -u $S
+               git-reset-script --hard $S
        }
 done
 if test -s $fail
 then
-       echo Some commits could not be rebased, check by hand:
-       cat $fail
+       echo >&2 Some commits could not be rebased, check by hand:
+       cat >&2 $fail
+       echo >&2 "(the same list of commits are found in $tmp)"
+       exit 1
+else
+       rm -f $fail
 fi
index 1c9a6315dd63dd27462aa7ca10880f7d3cb663c2..80628b538b85ecc6b188321f366471ad2ea8eb2d 100755 (executable)
@@ -5,28 +5,63 @@
 
 . git-sh-setup-script || die "Not a git archive"
        
-no_update_info=
+no_update_info= all_into_one= remove_redundant=
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
        -n)     no_update_info=t ;;
+       -a)     all_into_one=t ;;
+       -d)     remove_redandant=t ;;
        *)      break ;;
        esac
        shift
 done
 
 rm -f .tmp-pack-*
-packname=$(git-rev-list --unpacked --objects $(git-rev-parse --all) |
-       git-pack-objects --non-empty --incremental .tmp-pack) ||
+PACKDIR="$GIT_OBJECT_DIRECTORY/pack"
+
+# There will be more repacking strategies to come...
+case ",$all_into_one," in
+,,)
+       rev_list='--unpacked'
+       rev_parse='--all'
+       pack_objects='--incremental'
+       ;;
+,t,)
+       rev_list=
+       rev_parse='--all'
+       pack_objects=
+       # This part is a stop-gap until we have proper pack redundancy
+       # checker.
+       existing=`cd "$PACKDIR" && \
+           find . -type f \( -name '*.pack' -o -name '*.idx' \) -print`
+       ;;
+esac
+name=$(git-rev-list --objects $rev_list $(git-rev-parse $rev_parse) |
+       git-pack-objects --non-empty $pack_objects .tmp-pack) ||
        exit 1
-if [ -z "$packname" ]; then
-       echo Nothing new to pack
+if [ -z "$name" ]; then
+       echo Nothing new to pack.
        exit 0
 fi
+echo "Pack pack-$name created."
+
+mkdir -p "$PACKDIR" || exit
+
+mv .tmp-pack-$name.pack "$PACKDIR/pack-$name.pack" &&
+mv .tmp-pack-$name.idx  "$PACKDIR/pack-$name.idx" ||
+exit
+
+if test "$remove_redandant" = t
+then
+       # We know $existing are all redandant only when
+       # all-into-one is used.
+       if test "$all_into_one" != '' && test "$existing" != ''
+       then
+               ( cd "$PACKDIR" && rm -f $existing )
+       fi
+fi
 
-mkdir -p "$GIT_OBJECT_DIRECTORY/pack" &&
-mv .tmp-pack-$packname.pack "$GIT_OBJECT_DIRECTORY/pack/pack-$packname.pack" &&
-mv .tmp-pack-$packname.idx  "$GIT_OBJECT_DIRECTORY/pack/pack-$packname.idx" &&
 case "$no_update_info" in
 t) : ;;
 *) git-update-server-info ;;
index 22f2082fb1d9612fdc41b0edd7ec90327cfcf004..dd5866ec96b384c530f176a18e565e9700a344f8 100755 (executable)
 #!/bin/sh
+#
+# Copyright (c) 2005 Linus Torvalds
+# Copyright (c) 2005 Junio C Hamano
+#
 . git-sh-setup-script || die "Not a git archive"
 
-# We want a clean tree and clean index to be able to revert.
-status=$(git status)
-case "$status" in
-'nothing to commit') ;;
+case "$0" in
+*-revert-* )
+       me=revert ;;
+*-cherry-pick-* )
+       me=cherry-pick ;;
+esac
+
+usage () {
+       case "$me" in
+       cherry-pick)
+               die "usage git $me [-n] [-r] <commit-ish>"
+               ;;
+       revert)
+               die "usage git $me [-n] <commit-ish>"
+               ;;
+       esac
+}
+
+no_commit= replay=
+while case "$#" in 0) break ;; esac
+do
+       case "$1" in
+       -n|--n|--no|--no-|--no-c|--no-co|--no-com|--no-comm|\
+           --no-commi|--no-commit)
+               no_commit=t
+               ;;
+       -r|--r|--re|--rep|--repl|--repla|--replay)
+               replay=t
+               ;;
+       -*)
+               usage
+               ;;
+       *)
+               break
+               ;;
+       esac
+       shift
+done
+
+test "$me,$replay" = "revert,t" && usage
+
+case "$no_commit" in
+t)
+       # We do not intend to commit immediately.  We just want to
+       # merge the differences in.
+       head=$(git-write-tree) ||
+               die "Your index file is unmerged."
+       ;;
 *)
-       echo "$status"
-       die "Your working tree is dirty; cannot revert a previous patch." ;;
+       check_clean_tree || die "Cannot run $me from a dirty tree."
+       head=$(git-rev-parse --verify HEAD) ||
+               die "You do not have a valid HEAD"
+       ;;
 esac
 
 rev=$(git-rev-parse --verify "$@") &&
-commit=$(git-rev-parse --verify "$rev^0") || exit
-if git-diff-tree -R -M -p $commit | git-apply --index &&
-   msg=$(git-rev-list --pretty=oneline --max-count=1 $commit)
-then
-        {
-                echo "$msg" | sed -e '
-                       s/^[^ ]* /Revert "/
-                       s/$/"/'
-                echo
-                echo "This reverts $commit commit."
-                test "$rev" = "$commit" ||
-                echo "(original 'git revert' arguments: $@)"
-        } | git commit -F -
-else
-        # Now why did it fail?
-        parents=`git-cat-file commit "$commit" 2>/dev/null |
-                sed -ne '/^$/q;/^parent /p' |
-                wc -l`
-        case $parents in
-        0) die "Cannot revert the root commit nor non commit-ish." ;;
-        1) die "The patch does not apply." ;;
-        *) die "Cannot revert a merge commit." ;;
-        esac
-fi
+commit=$(git-rev-parse --verify "$rev^0") ||
+       die "Not a single commit $@"
+prev=$(git-rev-parse --verify "$commit^1" 2>/dev/null) ||
+       die "Cannot run $me a root commit"
+git-rev-parse --verify "$commit^2" >/dev/null 2>&1 &&
+       die "Cannot run $me a multi-parent commit."
+
+# "commit" is an existing commit.  We would want to apply
+# the difference it introduces since its first parent "prev"
+# on top of the current HEAD if we are cherry-pick.  Or the
+# reverse of it if we are revert.
+
+case "$me" in
+revert)
+       git-rev-list --pretty=oneline --max-count=1 $commit |
+       sed -e '
+               s/^[^ ]* /Revert "/
+               s/$/"/'
+       echo
+       echo "This reverts $commit commit."
+       test "$rev" = "$commit" ||
+       echo "(original 'git revert' arguments: $@)"
+       base=$commit next=$prev
+       ;;
+
+cherry-pick)
+       pick_author_script='
+       /^author /{
+               h
+               s/^author \([^<]*\) <[^>]*> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_NAME='\''&'\''/p
+
+               g
+               s/^author [^<]* <\([^>]*\)> .*$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_EMAIL='\''&'\''/p
+
+               g
+               s/^author [^<]* <[^>]*> \(.*\)$/\1/
+               s/'\''/'\''\'\'\''/g
+               s/.*/GIT_AUTHOR_DATE='\''&'\''/p
+
+               q
+       }'
+       set_author_env=`git-cat-file commit "$commit" |
+       sed -ne "$pick_author_script"`
+       eval "$set_author_env"
+       export GIT_AUTHOR_NAME
+       export GIT_AUTHOR_EMAIL
+       export GIT_AUTHOR_DATE
+
+       git-cat-file commit $commit | sed -e '1,/^$/d'
+       case "$replay" in
+       '')
+               echo "(cherry picked from $commit commit)"
+               test "$rev" = "$commit" ||
+               echo "(original 'git cherry-pick' arguments: $@)"
+               ;;
+       esac
+       base=$prev next=$commit
+       ;;
+
+esac >.msg
+
+# This three way merge is an interesting one.  We are at
+# $head, and would want to apply the change between $commit
+# and $prev on top of us (when reverting), or the change between
+# $prev and $commit on top of us (when cherry-picking or replaying).
+
+echo >&2 "First trying simple merge strategy to $me."
+git-read-tree -m -u $base $head $next &&
+result=$(git-write-tree 2>/dev/null) || {
+    echo >&2 "Simple $me fails; trying Automatic $me."
+    git-merge-cache -o git-merge-one-file-script -a || {
+           echo >&2 "Automatic $me failed.  After fixing it up,"
+           echo >&2 "you can use \"git commit -F .msg\""
+           case "$me" in
+           cherry-pick)
+               echo >&2 "You may choose to use the following when making"
+               echo >&2 "the commit:"
+               echo >&2 "$set_author_env"
+           esac
+           exit 1
+    }
+    result=$(git-write-tree) || exit
+}
+echo >&2 "Finished one $me."
+
+# If we are cherry-pick, and if the merge did not result in
+# hand-editing, we will hit this commit and inherit the original
+# author date and name.
+# If we are revert, or if our cherry-pick results in a hand merge,
+# we had better say that the current user is responsible for that.
+
+case "$no_commit" in
+'')
+       git commit -F .msg
+       rm -f .msg
+       ;;
+esac
index 84e15df1bcedba39e9310700d2aa7bf14dde2395..9ed5e851523a47aa3633494072e6295aa93adb01 100755 (executable)
@@ -11,6 +11,17 @@ die() {
        exit 1
 }
 
+check_clean_tree() {
+    dirty1_=`git-update-cache -q --refresh` && {
+    dirty2_=`git-diff-cache --name-only --cached HEAD`
+    case "$dirty2_" in '') : ;; *) (exit 1) ;; esac
+    } || {
+       echo >&2 "$dirty1_"
+       echo "$dirty2_" | sed >&2 -e 's/^/modified: /'
+       (exit 1)
+    }
+}
+
 [ -h "$GIT_DIR/HEAD" ] &&
 [ -d "$GIT_DIR/refs" ] &&
 [ -d "$GIT_OBJECT_DIRECTORY/00" ]
index 2a4e1768adc88e25dc108d6c9b323da2c52f5b1c..958a5e884afdf8bc21d672242d6bbee8d3c7340a 100644 (file)
@@ -35,25 +35,25 @@ static struct commit *pop_one_commit(struct commit_list **list_p)
 }
 
 struct commit_name {
-       int head_rev;   /* which head's ancestor? */
-       int generation; /* how many parents away from head_rev */
+       const char *head_name; /* which head's ancestor? */
+       int generation; /* how many parents away from head_name */
 };
 
-/* Name the commit as nth generation ancestor of head_rev;
+/* Name the commit as nth generation ancestor of head_name;
  * we count only the first-parent relationship for naming purposes.
  */
-static void name_commit(struct commit *commit, int head_rev, int nth)
+static void name_commit(struct commit *commit, const char *head_name, int nth)
 {
        struct commit_name *name;
        if (!commit->object.util)
                commit->object.util = xmalloc(sizeof(struct commit_name));
        name = commit->object.util;
-       name->head_rev = head_rev;
+       name->head_name = head_name;
        name->generation = nth;
 }
 
 /* Parent is the first parent of the commit.  We may name it
- * as (n+1)th generation ancestor of the same head_rev as
+ * as (n+1)th generation ancestor of the same head_name as
  * commit is nth generation ancestore of, if that generation
  * number is better than the name it already has.
  */
@@ -65,10 +65,88 @@ static void name_parent(struct commit *commit, struct commit *parent)
                return;
        if (!parent_name ||
            commit_name->generation + 1 < parent_name->generation)
-               name_commit(parent, commit_name->head_rev,
+               name_commit(parent, commit_name->head_name,
                            commit_name->generation + 1);
 }
 
+static int name_first_parent_chain(struct commit *c)
+{
+       int i = 0;
+       while (c) {
+               struct commit *p;
+               if (!c->object.util)
+                       break;
+               if (!c->parents)
+                       break;
+               p = c->parents->item;
+               if (!p->object.util) {
+                       name_parent(c, p);
+                       i++;
+               }
+               c = p;
+       }
+       return i;
+}
+
+static void name_commits(struct commit_list *list,
+                        struct commit **rev,
+                        char **ref_name,
+                        int num_rev)
+{
+       struct commit_list *cl;
+       struct commit *c;
+       int i;
+
+       /* First give names to the given heads */
+       for (cl = list; cl; cl = cl->next) {
+               c = cl->item;
+               if (c->object.util)
+                       continue;
+               for (i = 0; i < num_rev; i++) {
+                       if (rev[i] == c) {
+                               name_commit(c, ref_name[i], 0);
+                               break;
+                       }
+               }
+       }
+
+       /* Then commits on the first parent ancestry chain */
+       do {
+               i = 0;
+               for (cl = list; cl; cl = cl->next) {
+                       i += name_first_parent_chain(cl->item);
+               }
+       } while (i);
+
+       /* Finally, any unnamed commits */
+       do {
+               i = 0;
+               for (cl = list; cl; cl = cl->next) {
+                       struct commit_list *parents;
+                       struct commit_name *n;
+                       int nth;
+                       c = cl->item;
+                       if (!c->object.util)
+                               continue;
+                       n = c->object.util;
+                       parents = c->parents;
+                       nth = 0;
+                       while (parents) {
+                               struct commit *p = parents->item;
+                               char newname[1000];
+                               parents = parents->next;
+                               nth++;
+                               if (p->object.util)
+                                       continue;
+                               sprintf(newname, "%s^%d", n->head_name, nth);
+                               name_commit(p, strdup(newname), 0);
+                               i++;
+                               name_first_parent_chain(p);
+                       }
+               }
+       } while (i);
+}
+
 static int mark_seen(struct commit *commit, struct commit_list **seen_p)
 {
        if (!commit->object.flags) {
@@ -89,7 +167,6 @@ static void join_revs(struct commit_list **list_p,
                struct commit_list *parents;
                struct commit *commit = pop_one_commit(list_p);
                int flags = commit->object.flags & all_mask;
-               int nth_parent = 0;
                int still_interesting = !!interesting(*list_p);
 
                if (!still_interesting && extra < 0)
@@ -104,10 +181,6 @@ static void join_revs(struct commit_list **list_p,
                        struct commit *p = parents->item;
                        int this_flag = p->object.flags;
                        parents = parents->next;
-                       nth_parent++;
-                       if (nth_parent == 1)
-                               name_parent(commit, p);
-
                        if ((this_flag & flags) == flags)
                                continue;
                        parse_commit(p);
@@ -119,7 +192,7 @@ static void join_revs(struct commit_list **list_p,
        }
 }
 
-static void show_one_commit(struct commit *commit, char **head_name)
+static void show_one_commit(struct commit *commit)
 {
        char pretty[128], *cp;
        struct commit_name *name = commit->object.util;
@@ -129,8 +202,8 @@ static void show_one_commit(struct commit *commit, char **head_name)
                cp = pretty + 8;
        else
                cp = pretty;
-       if (name && head_name) {
-               printf("[%s", head_name[name->head_rev]);
+       if (name && name->head_name) {
+               printf("[%s", name->head_name);
                if (name->generation)
                        printf("~%d", name->generation);
                printf("] ");
@@ -237,6 +310,7 @@ int main(int ac, char **av)
        struct commit_list *list = NULL, *seen = NULL;
        int num_rev, i, extra = 0;
        int all_heads = 0, all_tags = 0;
+       int all_mask, all_revs, shown_merge_point;
        char head_path[128];
        int head_path_len;
        unsigned char head_sha1[20];
@@ -293,8 +367,6 @@ int main(int ac, char **av)
                        die("cannot find commit %s (%s)",
                            ref_name[num_rev], revkey);
                parse_commit(commit);
-               if (!commit->object.util)
-                       name_commit(commit, num_rev, 0);
                mark_seen(commit, &seen);
 
                /* rev#0 uses bit REV_SHIFT, rev#1 uses bit REV_SHIFT+1,
@@ -328,30 +400,44 @@ int main(int ac, char **av)
                        for (j = 0; j < i; j++)
                                putchar(' ');
                        printf("%c [%s] ", is_head ? '*' : '!', ref_name[i]);
-                       show_one_commit(rev[i], NULL);
+                       show_one_commit(rev[i]);
                }
                for (i = 0; i < num_rev; i++)
                        putchar('-');
                putchar('\n');
        }
 
-       label = ref_name;
+       /* Sort topologically */
+       sort_in_topological_order(&seen);
+
+       /* Give names to commits */
+       name_commits(seen, rev, ref_name, num_rev);
+
+       all_mask = ((1u << (REV_SHIFT + num_rev)) - 1);
+       all_revs = all_mask & ~((1u << REV_SHIFT) - 1);
+       shown_merge_point = 0;
+
        while (seen) {
                struct commit *commit = pop_one_commit(&seen);
                int this_flag = commit->object.flags;
+               int is_merge_point = (this_flag & all_revs) == all_revs;
                static char *obvious[] = { "" };
 
-               if ((this_flag & UNINTERESTING) && (--extra < 0))
-                       break;
+               if (is_merge_point)
+                       shown_merge_point = 1;
+
                if (1 < num_rev) {
                        for (i = 0; i < num_rev; i++)
                                putchar((this_flag & (1u << (i + REV_SHIFT)))
                                        ? '+' : ' ');
                        putchar(' ');
                }
-               show_one_commit(commit, label);
+               show_one_commit(commit);
                if (num_rev == 1)
                        label = obvious;
+               if (shown_merge_point && is_merge_point)
+                       if (--extra < 0)
+                               break;
        }
        return 0;
 }
index afcb00a3f5e25d062eb1219994506a669e252bc5..2b32dab5f54e8542fc6f5be481fd0f0f8f461137 100755 (executable)
@@ -9,7 +9,7 @@
 ## You give it a mbox-format collection of emails, and it will try to
 ## apply them to the kernel using "applypatch"
 ##
-## applymbox [ -k ] [ -q ] (-c .dotest/msg-number | mail_archive) [Signoff_file]"
+## applymbox [-u] [-k] [-q] (-c .dotest/msg-number | mail_archive) [Signoff_file]"
 ##
 ## The patch application may fail in the middle.  In which case:
 ## (1) look at .dotest/patch and fix it up to apply
 
 . git-sh-setup-script || die "Not a git archive"
 
-keep_subject= query_apply= continue= resume=t
+usage () {
+    echo >&2 "applymbox [-u] [-k] [-q] (-c .dotest/<num> | mbox) [signoff]"
+    exit 1
+}
+
+keep_subject= query_apply= continue= utf8= resume=t
 while case "$#" in 0) break ;; esac
 do
        case "$1" in
+       -u)     utf8=-u ;;
        -k)     keep_subject=-k ;;
        -q)     query_apply=t ;;
        -c)     continue="$2"; resume=f; shift ;;
@@ -64,7 +70,7 @@ do
     f,$i)      resume=t;;
     f,*)       continue;;
     *)
-           git-mailinfo $keep_subject \
+           git-mailinfo $keep_subject $utf8 \
                .dotest/msg .dotest/patch <$i >.dotest/info || exit 1
            git-stripspace < .dotest/msg > .dotest/msg-clean
            ;;
index a36123a1f5bc27e90debcf19d5348937c738d2f2..ef2add7ace9b047638c200f66d73bb52ea79bc05 100644 (file)
@@ -2,20 +2,32 @@
  * Another stupid program, this one parsing the headers of an
  * email to figure out authorship and subject
  */
+#define _GNU_SOURCE
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <ctype.h>
+#include <iconv.h>
 
 static FILE *cmitmsg, *patchfile;
 
 static int keep_subject = 0;
+static int metainfo_utf8 = 0;
 static char line[1000];
 static char date[1000];
 static char name[1000];
 static char email[1000];
 static char subject[1000];
 
+static enum  {
+       TE_DONTCARE, TE_QP, TE_BASE64,
+} transfer_encoding;
+static char charset[256];
+
+static char multipart_boundary[1000];
+static int multipart_boundary_len;
+static int patch_lines = 0;
+
 static char *sanity_check(char *name, char *email)
 {
        int len = strlen(name);
@@ -40,67 +52,188 @@ static int handle_from(char *line)
        if (*email && strchr(at+1, '@'))
                return 0;
 
+       /* Pick up the string around '@', possibly delimited with <>
+        * pair; that is the email part.  White them out while copying.
+        */
        while (at > line) {
                char c = at[-1];
-               if (isspace(c) || c == '<')
+               if (isspace(c))
+                       break;
+               if (c == '<') {
+                       at[-1] = ' ';
                        break;
+               }
                at--;
        }
        dst = email;
        for (;;) {
                unsigned char c = *at;
-               if (!c || c == '>' || isspace(c))
+               if (!c || c == '>' || isspace(c)) {
+                       if (c == '>')
+                               *at = ' ';
                        break;
+               }
                *at++ = ' ';
                *dst++ = c;
        }
        *dst++ = 0;
 
+       /* The remainder is name.  It could be "John Doe <john.doe@xz>"
+        * or "john.doe@xz (John Doe)", but we have whited out the
+        * email part, so trim from both ends, possibly removing
+        * the () pair at the end.
+        */
        at = line + strlen(line);
        while (at > line) {
                unsigned char c = *--at;
-               if (isalnum(c))
+               if (!isspace(c)) {
+                       at[(c == ')') ? 0 : 1] = 0;
                        break;
-               *at = 0;
+               }
        }
 
        at = line;
        for (;;) {
                unsigned char c = *at;
-               if (!c)
-                       break;
-               if (isalnum(c))
+               if (!c || !isspace(c)) {
+                       if (c == '(')
+                               at++;
                        break;
+               }
                at++;
        }
-
        at = sanity_check(at, email);
-       
        strcpy(name, at);
        return 1;
 }
 
-static void handle_date(char *line)
+static int handle_date(char *line)
 {
        strcpy(date, line);
+       return 0;
 }
 
-static void handle_subject(char *line)
+static int handle_subject(char *line)
 {
        strcpy(subject, line);
+       return 0;
+}
+
+/* NOTE NOTE NOTE.  We do not claim we do full MIME.  We just attempt
+ * to have enough heuristics to grok MIME encoded patches often found
+ * on our mailing lists.  For example, we do not even treat header lines
+ * case insensitively.
+ */
+
+static int slurp_attr(const char *line, const char *name, char *attr)
+{
+       char *ends, *ap = strcasestr(line, name);
+       size_t sz;
+
+       if (!ap) {
+               *attr = 0;
+               return 0;
+       }
+       ap += strlen(name);
+       if (*ap == '"') {
+               ap++;
+               ends = "\"";
+       }
+       else
+               ends = "; \t";
+       sz = strcspn(ap, ends);
+       memcpy(attr, ap, sz);
+       attr[sz] = 0;
+       return 1;
+}
+
+static int handle_subcontent_type(char *line)
+{
+       /* We do not want to mess with boundary.  Note that we do not
+        * handle nested multipart.
+        */
+       slurp_attr(line, "charset=", charset);
+       if (*charset) {
+               int i, c;
+               for (i = 0; (c = charset[i]) != 0; i++)
+                       charset[i] = tolower(c);
+       }
+       return 0;
+}
+
+static int handle_content_type(char *line)
+{
+       *multipart_boundary = 0;
+       if (slurp_attr(line, "boundary=", multipart_boundary + 2)) {
+               memcpy(multipart_boundary, "--", 2);
+               multipart_boundary_len = strlen(multipart_boundary);
+       }
+       slurp_attr(line, "charset=", charset);
+       return 0;
+}
+
+static int handle_content_transfer_encoding(char *line)
+{
+       if (strcasestr(line, "base64"))
+               transfer_encoding = TE_BASE64;
+       else if (strcasestr(line, "quoted-printable"))
+               transfer_encoding = TE_QP;
+       else
+               transfer_encoding = TE_DONTCARE;
+       return 0;
 }
 
-static void check_line(char *line, int len)
+static int is_multipart_boundary(const char *line)
+{
+       return (!memcmp(line, multipart_boundary, multipart_boundary_len));
+}
+
+static int eatspace(char *line)
 {
-       if (!memcmp(line, "From:", 5) && isspace(line[5]))
-               handle_from(line+6);
-       else if (!memcmp(line, "Date:", 5) && isspace(line[5]))
-               handle_date(line+6);
-       else if (!memcmp(line, "Subject:", 8) && isspace(line[8]))
-               handle_subject(line+9);
+       int len = strlen(line);
+       while (len > 0 && isspace(line[len-1]))
+               line[--len] = 0;
+       return len;
 }
 
-static char * cleanup_subject(char *subject)
+#define SEEN_FROM 01
+#define SEEN_DATE 02
+#define SEEN_SUBJECT 04
+
+/* First lines of body can have From:, Date:, and Subject: */
+static int handle_inbody_header(int *seen, char *line)
+{
+       if (!memcmp("From:", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_FROM) && handle_from(line+6)) {
+                       *seen |= SEEN_FROM;
+                       return 1;
+               }
+       }
+       if (!memcmp("Date:", line, 5) && isspace(line[5])) {
+               if (!(*seen & SEEN_DATE)) {
+                       handle_date(line+6);
+                       *seen |= SEEN_DATE;
+                       return 1;
+               }
+       }
+       if (!memcmp("Subject:", line, 8) && isspace(line[8])) {
+               if (!(*seen & SEEN_SUBJECT)) {
+                       handle_subject(line+9);
+                       *seen |= SEEN_SUBJECT;
+                       return 1;
+               }
+       }
+       if (!memcmp("[PATCH]", line, 7) && isspace(line[7])) {
+               if (!(*seen & SEEN_SUBJECT)) {
+                       handle_subject(line);
+                       *seen |= SEEN_SUBJECT;
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+static char *cleanup_subject(char *subject)
 {
        if (keep_subject)
                return subject;
@@ -153,109 +286,435 @@ static void cleanup_space(char *buf)
        }
 }
 
-static void handle_rest(void)
+typedef int (*header_fn_t)(char *);
+struct header_def {
+       const char *name;
+       header_fn_t func;
+       int namelen;
+};
+
+static void check_header(char *line, int len, struct header_def *header)
+{
+       int i;
+
+       if (header[0].namelen <= 0) {
+               for (i = 0; header[i].name; i++)
+                       header[i].namelen = strlen(header[i].name);
+       }
+       for (i = 0; header[i].name; i++) {
+               int len = header[i].namelen;
+               if (!strncasecmp(line, header[i].name, len) &&
+                   line[len] == ':' && isspace(line[len + 1])) {
+                       header[i].func(line + len + 2);
+                       break;
+               }
+       }
+}
+
+static void check_subheader_line(char *line, int len)
+{
+       static struct header_def header[] = {
+               { "Content-Type", handle_subcontent_type },
+               { "Content-Transfer-Encoding",
+                 handle_content_transfer_encoding },
+               { NULL },
+       };
+       check_header(line, len, header);
+}
+static void check_header_line(char *line, int len)
+{
+       static struct header_def header[] = {
+               { "From", handle_from },
+               { "Date", handle_date },
+               { "Subject", handle_subject },
+               { "Content-Type", handle_content_type },
+               { "Content-Transfer-Encoding",
+                 handle_content_transfer_encoding },
+               { NULL },
+       };
+       check_header(line, len, header);
+}
+
+static int read_one_header_line(char *line, int sz, FILE *in)
+{
+       int ofs = 0;
+       while (ofs < sz) {
+               int peek, len;
+               if (fgets(line + ofs, sz - ofs, in) == NULL)
+                       return ofs;
+               len = eatspace(line + ofs);
+               if (len == 0)
+                       return ofs;
+               peek = fgetc(in); ungetc(peek, in);
+               if (peek == ' ' || peek == '\t') {
+                       /* Yuck, 2822 header "folding" */
+                       ofs += len;
+                       continue;
+               }
+               return ofs + len;
+       }
+       return ofs;
+}
+
+static unsigned hexval(int c)
+{
+       if (c >= '0' && c <= '9')
+               return c - '0';
+       if (c >= 'a' && c <= 'f')
+               return c - 'a' + 10;
+       if (c >= 'A' && c <= 'F')
+               return c - 'A' + 10;
+       return ~0;
+}
+
+static int decode_q_segment(char *in, char *ot, char *ep)
+{
+       int c;
+       while ((c = *in++) != 0 && (in <= ep)) {
+               if (c == '=') {
+                       int d = *in++;
+                       if (d == '\n' || !d)
+                               break; /* drop trailing newline */
+                       *ot++ = ((hexval(d) << 4) | hexval(*in++));
+               }
+               else
+                       *ot++ = c;
+       }
+       *ot = 0;
+       return 0;
+}
+
+static int decode_b_segment(char *in, char *ot, char *ep)
+{
+       /* Decode in..ep, possibly in-place to ot */
+       int c, pos = 0, acc = 0;
+
+       while ((c = *in++) != 0 && (in <= ep)) {
+               if (c == '+')
+                       c = 62;
+               else if (c == '/')
+                       c = 63;
+               else if ('A' <= c && c <= 'Z')
+                       c -= 'A';
+               else if ('a' <= c && c <= 'z')
+                       c -= 'a' - 26;
+               else if ('0' <= c && c <= '9')
+                       c -= '0' - 52;
+               else if (c == '=') {
+                       /* padding is almost like (c == 0), except we do
+                        * not output NUL resulting only from it;
+                        * for now we just trust the data.
+                        */
+                       c = 0;
+               }
+               else
+                       continue; /* garbage */
+               switch (pos++) {
+               case 0:
+                       acc = (c << 2);
+                       break;
+               case 1:
+                       *ot++ = (acc | (c >> 4));
+                       acc = (c & 15) << 4;
+                       break;
+               case 2:
+                       *ot++ = (acc | (c >> 2));
+                       acc = (c & 3) << 6;
+                       break;
+               case 3:
+                       *ot++ = (acc | c);
+                       acc = pos = 0;
+                       break;
+               }
+       }
+       *ot = 0;
+       return 0;
+}
+
+static void convert_to_utf8(char *line, char *charset)
+{
+       if (*charset) {
+               char *in, *out;
+               size_t insize, outsize, nrc;
+               char outbuf[4096]; /* cheat */
+               iconv_t conv = iconv_open("utf-8", charset);
+
+               if (conv == (iconv_t) -1) {
+                       fprintf(stderr, "cannot convert from %s to utf-8\n",
+                               charset);
+                       *charset = 0;
+                       return;
+               }
+               in = line;
+               insize = strlen(in);
+               out = outbuf;
+               outsize = sizeof(outbuf);
+               nrc = iconv(conv, &in, &insize, &out, &outsize);
+               iconv_close(conv);
+               if (nrc == (size_t) -1)
+                       return;
+               *out = 0;
+               strcpy(line, outbuf);
+       }
+}
+
+static void decode_header_bq(char *it)
+{
+       char *in, *out, *ep, *cp, *sp;
+       char outbuf[1000];
+
+       in = it;
+       out = outbuf;
+       while ((ep = strstr(in, "=?")) != NULL) {
+               int sz, encoding;
+               char charset_q[256], piecebuf[256];
+               if (in != ep) {
+                       sz = ep - in;
+                       memcpy(out, in, sz);
+                       out += sz;
+                       in += sz;
+               }
+               /* E.g.
+                * ep : "=?iso-2022-jp?B?GyR...?= foo"
+                * ep : "=?ISO-8859-1?Q?Foo=FCbar?= baz"
+                */
+               ep += 2;
+               cp = strchr(ep, '?');
+               if (!cp)
+                       return; /* no munging */
+               for (sp = ep; sp < cp; sp++)
+                       charset_q[sp - ep] = tolower(*sp);
+               charset_q[cp - ep] = 0;
+               encoding = cp[1];
+               if (!encoding || cp[2] != '?')
+                       return; /* no munging */
+               ep = strstr(cp + 3, "?=");
+               if (!ep)
+                       return; /* no munging */
+               switch (tolower(encoding)) {
+               default:
+                       return; /* no munging */
+               case 'b':
+                       sz = decode_b_segment(cp + 3, piecebuf, ep);
+                       break;
+               case 'q':
+                       sz = decode_q_segment(cp + 3, piecebuf, ep);
+                       break;
+               }
+               if (sz < 0)
+                       return;
+               if (metainfo_utf8)
+                       convert_to_utf8(piecebuf, charset_q);
+               strcpy(out, piecebuf);
+               out += strlen(out);
+               in = ep + 2;
+       }
+       strcpy(out, in);
+       strcpy(it, outbuf);
+}
+
+static void decode_transfer_encoding(char *line)
+{
+       char *ep;
+
+       switch (transfer_encoding) {
+       case TE_QP:
+               ep = line + strlen(line);
+               decode_q_segment(line, line, ep);
+               break;
+       case TE_BASE64:
+               ep = line + strlen(line);
+               decode_b_segment(line, line, ep);
+               break;
+       case TE_DONTCARE:
+               break;
+       }
+}
+
+static void handle_info(void)
 {
-       FILE *out = cmitmsg;
-       char *sub = cleanup_subject(subject);
+       char *sub;
+       static int done_info = 0;
+
+       if (done_info)
+               return;
+
+       done_info = 1;
+       sub = cleanup_subject(subject);
        cleanup_space(name);
        cleanup_space(date);
        cleanup_space(email);
        cleanup_space(sub);
-       printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n", name, email, sub, date);
 
+       /* Unwrap inline B and Q encoding, and optionally
+        * normalize the meta information to utf8.
+        */
+       decode_header_bq(name);
+       decode_header_bq(date);
+       decode_header_bq(email);
+       decode_header_bq(sub);
+       printf("Author: %s\nEmail: %s\nSubject: %s\nDate: %s\n\n",
+              name, email, sub, date);
+}
+
+/* We are inside message body and have read line[] already.
+ * Spit out the commit log.
+ */
+static int handle_commit_msg(void)
+{
+       if (!cmitmsg)
+               return 0;
        do {
                if (!memcmp("diff -", line, 6) ||
                    !memcmp("---", line, 3) ||
                    !memcmp("Index: ", line, 7))
-                       out = patchfile;
+                       break;
+               if ((multipart_boundary[0] && is_multipart_boundary(line))) {
+                       /* We come here when the first part had only
+                        * the commit message without any patch.  We
+                        * pretend we have not seen this line yet, and
+                        * go back to the loop.
+                        */
+                       return 1;
+               }
 
-               fputs(line, out);
+               /* Unwrap transfer encoding and optionally
+                * normalize the log message to UTF-8.
+                */
+               decode_transfer_encoding(line);
+               if (metainfo_utf8)
+                       convert_to_utf8(line, charset);
+               fputs(line, cmitmsg);
        } while (fgets(line, sizeof(line), stdin) != NULL);
-
-       if (out == cmitmsg) {
-               fprintf(stderr, "No patch found\n");
-               exit(1);
-       }
-
        fclose(cmitmsg);
-       fclose(patchfile);
+       cmitmsg = NULL;
+       return 0;
 }
 
-static int eatspace(char *line)
+/* We have done the commit message and have the first
+ * line of the patch in line[].
+ */
+static void handle_patch(void)
 {
-       int len = strlen(line);
-       while (len > 0 && isspace(line[len-1]))
-               line[--len] = 0;
-       return len;
+       do {
+               if (multipart_boundary[0] && is_multipart_boundary(line))
+                       break;
+               /* Only unwrap transfer encoding but otherwise do not
+                * do anything.  We do *NOT* want UTF-8 conversion
+                * here; we are dealing with the user payload.
+                */
+               decode_transfer_encoding(line);
+               fputs(line, patchfile);
+               patch_lines++;
+       } while (fgets(line, sizeof(line), stdin) != NULL);
 }
 
-static void handle_body(void)
+/* multipart boundary and transfer encoding are set up for us, and we
+ * are at the end of the sub header.  do equivalent of handle_body up
+ * to the next boundary without closing patchfile --- we will expect
+ * that the first part to contain commit message and a patch, and
+ * handle other parts as pure patches.
+ */
+static int handle_multipart_one_part(void)
 {
-       int has_from = 0;
-       int has_date = 0;
+       int seen = 0;
+       int n = 0;
+       int len;
 
-       /* First lines of body can have From: and Date: */
        while (fgets(line, sizeof(line), stdin) != NULL) {
-               int len = eatspace(line);
+       again:
+               len = eatspace(line);
+               n++;
                if (!len)
                        continue;
-               if (!memcmp("From:", line, 5) && isspace(line[5])) {
-                       if (!has_from && handle_from(line+6)) {
-                               has_from = 1;
-                               continue;
-                       }
-               }
-               if (!memcmp("Date:", line, 5) && isspace(line[5])) {
-                       if (!has_date) {
-                               handle_date(line+6);
-                               has_date = 1;
-                               continue;
-                       }
-               }
+               if (is_multipart_boundary(line))
+                       break;
+               if (0 <= seen && handle_inbody_header(&seen, line))
+                       continue;
+               seen = -1; /* no more inbody headers */
                line[len] = '\n';
-               handle_rest();
+               handle_info();
+               if (handle_commit_msg())
+                       goto again;
+               handle_patch();
                break;
        }
+       if (n == 0)
+               return -1;
+       return 0;
 }
 
-static int read_one_header_line(char *line, int sz, FILE *in)
+static void handle_multipart_body(void)
 {
-       int ofs = 0;
-       while (ofs < sz) {
-               int peek, len;
-               if (fgets(line + ofs, sz - ofs, in) == NULL)
-                       return ofs;
-               len = eatspace(line + ofs);
-               if (len == 0)
-                       return ofs;
-               peek = fgetc(in); ungetc(peek, in);
-               if (peek == ' ' || peek == '\t') {
-                       /* Yuck, 2822 header "folding" */
-                       ofs += len;
-                       continue;
+       int part_num = 0;
+
+       /* Skip up to the first boundary */
+       while (fgets(line, sizeof(line), stdin) != NULL)
+               if (is_multipart_boundary(line)) {
+                       part_num = 1;
+                       break;
                }
-               return ofs + len;
+       if (!part_num)
+               return;
+       /* We are on boundary line.  Start slurping the subhead. */
+       while (1) {
+               int len = read_one_header_line(line, sizeof(line), stdin);
+               if (!len) {
+                       if (handle_multipart_one_part() < 0)
+                               return;
+               }
+               else
+                       check_subheader_line(line, len);
+       }
+       fclose(patchfile);
+       if (!patch_lines) {
+               fprintf(stderr, "No patch found\n");
+               exit(1);
        }
-       return ofs;
 }
 
-static void usage(void)
+/* Non multipart message */
+static void handle_body(void)
 {
-       fprintf(stderr, "mailinfo msg-file patch-file < email\n");
-       exit(1);
+       int seen = 0;
+
+       while (fgets(line, sizeof(line), stdin) != NULL) {
+               int len = eatspace(line);
+               if (!len)
+                       continue;
+               if (0 <= seen && handle_inbody_header(&seen, line))
+                       continue;
+               seen = -1; /* no more inbody headers */
+               line[len] = '\n';
+               handle_info();
+               handle_commit_msg();
+               handle_patch();
+               break;
+       }
+       fclose(patchfile);
+       if (!patch_lines) {
+               fprintf(stderr, "No patch found\n");
+               exit(1);
+       }
 }
 
 static const char mailinfo_usage[] =
-"git-mailinfo [-k] msg patch <mail >info";
-int main(int argc, char ** argv)
+       "git-mailinfo [-k] [-u] msg patch <mail >info";
+
+static void usage(void) {
+       fprintf(stderr, "%s\n", mailinfo_usage);
+       exit(1);
+}
+
+int main(int argc, char **argv)
 {
        while (1 < argc && argv[1][0] == '-') {
                if (!strcmp(argv[1], "-k"))
                        keep_subject = 1;
-               else {
-                       fprintf(stderr, "usage: %s\n", mailinfo_usage);
-                       exit(1);
-               }
+               else if (!strcmp(argv[1], "-u"))
+                       metainfo_utf8 = 1;
+               else
+                       usage();
                argc--; argv++;
        }
 
@@ -274,10 +733,13 @@ int main(int argc, char ** argv)
        while (1) {
                int len = read_one_header_line(line, sizeof(line), stdin);
                if (!len) {
-                       handle_body();
+                       if (multipart_boundary[0])
+                               handle_multipart_body();
+                       else
+                               handle_body();
                        break;
                }
-               check_line(line, len);
+               check_header_line(line, len);
        }
        return 0;
 }