aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJunio C Hamano <junkio@cox.net>2005-08-29 19:09:48 -0700
committerJunio C Hamano <junkio@cox.net>2005-08-29 19:09:48 -0700
commit7a034337420a45395b3f68a1f12ca53d8b0652be (patch)
treefbf3ba4561edd1e1d847417c57b1cdade98c887b
parent5dd02f9404e41055b4e7bb8106e9c58dcc524021 (diff)
parentf85a41916163097c3c307ac45d1a935564c52bf0 (diff)
downloadgit-7a034337420a45395b3f68a1f12ca53d8b0652be.tar.gz
git-7a034337420a45395b3f68a1f12ca53d8b0652be.tar.xz
Merge refs/heads/master from .
-rw-r--r--Documentation/git-apply-patch-script.txt32
-rw-r--r--Documentation/git-applymbox.txt54
-rw-r--r--Documentation/git-applypatch.txt22
-rw-r--r--Documentation/git-bisect-script.txt62
-rw-r--r--Documentation/git-cherry-pick-script.txt57
-rw-r--r--Documentation/git-mailinfo.txt43
-rw-r--r--Documentation/git-prune-script.txt22
-rw-r--r--Documentation/git-repack-script.txt33
-rw-r--r--Documentation/git-revert-script.txt16
-rw-r--r--Documentation/git-show-branch.txt5
-rw-r--r--Documentation/git.txt205
-rw-r--r--Documentation/howto/rebase-from-internal-branch.txt2
-rw-r--r--Makefile3
-rwxr-xr-xgit-apply-patch-script144
-rwxr-xr-xgit-cherry16
-rwxr-xr-xgit-rebase-script25
-rwxr-xr-xgit-repack-script51
-rwxr-xr-xgit-revert-script187
-rwxr-xr-xgit-sh-setup-script11
-rw-r--r--show-branch.c130
-rwxr-xr-xtools/git-applymbox12
-rw-r--r--tools/mailinfo.c628
22 files changed, 1264 insertions, 496 deletions
diff --git a/Documentation/git-apply-patch-script.txt b/Documentation/git-apply-patch-script.txt
deleted file mode 100644
index 808d3cdc1..000000000
--- a/Documentation/git-apply-patch-script.txt
+++ /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
-
diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt
index acf0bf3d6..3d813ec4c 100644
--- a/Documentation/git-applymbox.txt
+++ b/Documentation/git-applymbox.txt
@@ -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
------
diff --git a/Documentation/git-applypatch.txt b/Documentation/git-applypatch.txt
index 55facd2c7..14ce53aad 100644
--- a/Documentation/git-applypatch.txt
+++ b/Documentation/git-applypatch.txt
@@ -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
diff --git a/Documentation/git-bisect-script.txt b/Documentation/git-bisect-script.txt
index 1f5e38626..1b0345199 100644
--- a/Documentation/git-bisect-script.txt
+++ b/Documentation/git-bisect-script.txt
@@ -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
index 000000000..ab9fb227d
--- /dev/null
+++ b/Documentation/git-cherry-pick-script.txt
@@ -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
+
diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt
index b75f61c84..eb3a3d1be 100644
--- a/Documentation/git-mailinfo.txt
+++ b/Documentation/git-mailinfo.txt
@@ -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
--------------
diff --git a/Documentation/git-prune-script.txt b/Documentation/git-prune-script.txt
index e8305a6f3..0f76e8fd0 100644
--- a/Documentation/git-prune-script.txt
+++ b/Documentation/git-prune-script.txt
@@ -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
diff --git a/Documentation/git-repack-script.txt b/Documentation/git-repack-script.txt
index 497d0dfe8..cd449bcc2 100644
--- a/Documentation/git-repack-script.txt
+++ b/Documentation/git-repack-script.txt
@@ -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
------
diff --git a/Documentation/git-revert-script.txt b/Documentation/git-revert-script.txt
index 727073e27..9609dcd32 100644
--- a/Documentation/git-revert-script.txt
+++ b/Documentation/git-revert-script.txt
@@ -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>
diff --git a/Documentation/git-show-branch.txt b/Documentation/git-show-branch.txt
index 71d5ba8fd..5103c0c4f 100644
--- a/Documentation/git-show-branch.txt
+++ b/Documentation/git-show-branch.txt
@@ -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
diff --git a/Documentation/git.txt b/Documentation/git.txt
index f63cbdd37..befe3e52c 100644
--- a/Documentation/git.txt
+++ b/Documentation/git.txt
@@ -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.
diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt
index f627e4271..4523b69d4 100644
--- a/Documentation/howto/rebase-from-internal-branch.txt
+++ b/Documentation/howto/rebase-from-internal-branch.txt
@@ -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
diff --git a/Makefile b/Makefile
index 57d4f1170..f13ff5aa2 100644
--- 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
index 6261fd85f..000000000
--- a/git-apply-patch-script
+++ /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
diff --git a/git-cherry b/git-cherry
index e18636364..fe8c1090e 100755
--- a/git-cherry
+++ b/git-cherry
@@ -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
diff --git a/git-rebase-script b/git-rebase-script
index a335b9917..b0893cc10 100755
--- a/git-rebase-script
+++ b/git-rebase-script
@@ -37,25 +37,32 @@ git-rev-parse --verify "$upstream^0" >"$GIT_DIR/HEAD" || exit
tmp=.rebase-tmp$$
fail=$tmp-fail
-trap "rm -rf $tmp-*" 0 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
diff --git a/git-repack-script b/git-repack-script
index 1c9a6315d..80628b538 100755
--- a/git-repack-script
+++ b/git-repack-script
@@ -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 ;;
diff --git a/git-revert-script b/git-revert-script
index 22f2082fb..dd5866ec9 100755
--- a/git-revert-script
+++ b/git-revert-script
@@ -1,37 +1,164 @@
#!/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
diff --git a/git-sh-setup-script b/git-sh-setup-script
index 84e15df1b..9ed5e8515 100755
--- a/git-sh-setup-script
+++ b/git-sh-setup-script
@@ -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" ]
diff --git a/show-branch.c b/show-branch.c
index 2a4e1768a..958a5e884 100644
--- a/show-branch.c
+++ b/show-branch.c
@@ -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;
}
diff --git a/tools/git-applymbox b/tools/git-applymbox
index afcb00a3f..2b32dab5f 100755
--- a/tools/git-applymbox
+++ b/tools/git-applymbox
@@ -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
@@ -20,10 +20,16 @@
. 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
;;
diff --git a/tools/mailinfo.c b/tools/mailinfo.c
index a36123a1f..ef2add7ac 100644
--- a/tools/mailinfo.c
+++ b/tools/mailinfo.c
@@ -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;
}