diff options
author | Junio C Hamano <junkio@cox.net> | 2005-08-29 19:09:48 -0700 |
---|---|---|
committer | Junio C Hamano <junkio@cox.net> | 2005-08-29 19:09:48 -0700 |
commit | 7a034337420a45395b3f68a1f12ca53d8b0652be (patch) | |
tree | fbf3ba4561edd1e1d847417c57b1cdade98c887b | |
parent | 5dd02f9404e41055b4e7bb8106e9c58dcc524021 (diff) | |
parent | f85a41916163097c3c307ac45d1a935564c52bf0 (diff) | |
download | git-7a034337420a45395b3f68a1f12ca53d8b0652be.tar.gz git-7a034337420a45395b3f68a1f12ca53d8b0652be.tar.xz |
Merge refs/heads/master from .
-rw-r--r-- | Documentation/git-apply-patch-script.txt | 32 | ||||
-rw-r--r-- | Documentation/git-applymbox.txt | 54 | ||||
-rw-r--r-- | Documentation/git-applypatch.txt | 22 | ||||
-rw-r--r-- | Documentation/git-bisect-script.txt | 62 | ||||
-rw-r--r-- | Documentation/git-cherry-pick-script.txt | 57 | ||||
-rw-r--r-- | Documentation/git-mailinfo.txt | 43 | ||||
-rw-r--r-- | Documentation/git-prune-script.txt | 22 | ||||
-rw-r--r-- | Documentation/git-repack-script.txt | 33 | ||||
-rw-r--r-- | Documentation/git-revert-script.txt | 16 | ||||
-rw-r--r-- | Documentation/git-show-branch.txt | 5 | ||||
-rw-r--r-- | Documentation/git.txt | 205 | ||||
-rw-r--r-- | Documentation/howto/rebase-from-internal-branch.txt | 2 | ||||
-rw-r--r-- | Makefile | 3 | ||||
-rwxr-xr-x | git-apply-patch-script | 144 | ||||
-rwxr-xr-x | git-cherry | 16 | ||||
-rwxr-xr-x | git-rebase-script | 25 | ||||
-rwxr-xr-x | git-repack-script | 51 | ||||
-rwxr-xr-x | git-revert-script | 187 | ||||
-rwxr-xr-x | git-sh-setup-script | 11 | ||||
-rw-r--r-- | show-branch.c | 130 | ||||
-rwxr-xr-x | tools/git-applymbox | 12 | ||||
-rw-r--r-- | tools/mailinfo.c | 628 |
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 @@ -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; } |