From 48313592bf318139b39d6017a863f27420e3939a Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sat, 27 Aug 2005 23:53:27 -0700 Subject: [PATCH 1/7] Redo "revert" using three-way merge machinery. The reverse patch application using "git apply" sometimes is too rigid. Since the user would get used to resolving conflicting merges by hand during the normal merge experience, using the same machinery would be more helpful rather than just giving up. Cherry-picking and reverting are essentially the same operation. You pick one commit, and apply the difference that commit introduces to its own commit ancestry chain to the current tree. Revert applies the diff in reverse while cherry-pick applies it forward. They share the same logic, just different messages and merge direction. Rewrite "git rebase" using "git cherry-pick". Signed-off-by: Junio C Hamano --- Makefile | 1 + git-rebase-script | 25 +++--- git-revert-script | 187 +++++++++++++++++++++++++++++++++++++------- git-sh-setup-script | 11 +++ 4 files changed, 185 insertions(+), 39 deletions(-) diff --git a/Makefile b/Makefile index c0fdaa701f..13ad6d89ee 100644 --- a/Makefile +++ b/Makefile @@ -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-rebase-script b/git-rebase-script index a335b99178..b0893cc10f 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-revert-script b/git-revert-script index 22f2082fb1..dd5866ec96 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] " + ;; + revert) + die "usage git $me [-n] " + ;; + 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 84e15df1bc..9ed5e85152 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" ] From e47e35acdda5e334befcde6ffaf207cb5a63b6f4 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Fri, 26 Aug 2005 17:27:07 -0700 Subject: [PATCH 2/7] Remove git-apply-patch-script. Now the rebase is rewritten to use git cherry-pick, there is no user for that ancient script. I've checked Cogito and StGIT to make sure they do not use it. Signed-off-by: Junio C Hamano --- Documentation/git-apply-patch-script.txt | 32 ---- Documentation/git.txt | 3 - .../howto/rebase-from-internal-branch.txt | 2 +- Makefile | 2 +- git-apply-patch-script | 144 ------------------ git-cherry | 16 +- 6 files changed, 5 insertions(+), 194 deletions(-) delete mode 100644 Documentation/git-apply-patch-script.txt delete mode 100755 git-apply-patch-script diff --git a/Documentation/git-apply-patch-script.txt b/Documentation/git-apply-patch-script.txt deleted file mode 100644 index 808d3cdc1b..0000000000 --- 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 - -Documentation --------------- -Documentation by David Greaves, Junio C Hamano and the git-list . - -GIT ---- -Part of the link:git.html[git] suite - diff --git a/Documentation/git.txt b/Documentation/git.txt index f63cbdd372..807a470076 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -260,9 +260,6 @@ 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-convert-cache.html[git-convert-cache]:: Converts old-style GIT repository diff --git a/Documentation/howto/rebase-from-internal-branch.txt b/Documentation/howto/rebase-from-internal-branch.txt index f627e4271c..4523b69d4f 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 13ad6d89ee..007d42b038 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 \ diff --git a/git-apply-patch-script b/git-apply-patch-script deleted file mode 100755 index 6261fd85f2..0000000000 --- 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 e186363647..fe8c1090e9 100755 --- a/git-cherry +++ b/git-cherry @@ -14,19 +14,9 @@ usage="usage: $0 "'[-v] [] Each commit between the fork-point and is examined, and compared against the change each commit between the fork-point and - 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 + 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 From de2b82c65dac0e9768bc32f38e96b61d0b778843 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Aug 2005 03:01:09 -0700 Subject: [PATCH 3/7] Document "git cherry-pick" and "git revert" Signed-off-by: Junio C Hamano --- Documentation/git-cherry-pick-script.txt | 57 ++++++++++++++++++++++++ Documentation/git-revert-script.txt | 16 ++++++- Documentation/git.txt | 3 ++ 3 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 Documentation/git-cherry-pick-script.txt diff --git a/Documentation/git-cherry-pick-script.txt b/Documentation/git-cherry-pick-script.txt new file mode 100644 index 0000000000..ab9fb227da --- /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] + +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 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 + +Documentation +-------------- +Documentation by Junio C Hamano and the git-list . + +GIT +--- +Part of the link:git.html[git] suite + diff --git a/Documentation/git-revert-script.txt b/Documentation/git-revert-script.txt index 727073e279..9609dcd325 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' +'git-revert-script' [-n] DESCRIPTION ----------- @@ -20,6 +20,20 @@ OPTIONS :: 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 diff --git a/Documentation/git.txt b/Documentation/git.txt index 807a470076..221889bc75 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -198,6 +198,9 @@ Porcelain-ish Commands link:git-revert-script.html[git-revert-script]:: Revert an existing commit. +link:git-cherry-pick-script.html[git-cherry-pick-script]:: + Cherry-pick the effect of an existing commit. + link:git-rebase-script.html[git-rebase-script]:: Rebase local commits to new upstream head. From d4a9ce78f6d516568bfca4d808033d9e8254c33f Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Sun, 28 Aug 2005 12:33:16 -0700 Subject: [PATCH 4/7] mailinfo and applymbox updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This attempts to minimally cope with a subset of MIME "features" often seen in patches sent to our mailing lists. Namely: - People's name spelled in characters outside ASCII (both on From: header and the signed-off-by line). - Content-transfer-encoding using quoted-printable (both in multipart and non-multipart messages). These MIME features are detected and decoded by "git mailinfo". Optionally, with the '-u' flag, the output to .info and .msg is transliterated from its original chaset to utf-8. This is to encourage people to use utf8 in their commit messages for interoperability. Applymbox accepts additional flag '-u' which is passed to mailinfo. Signed-off-by: Junio C Hamano / 濱野 純 --- tools/git-applymbox | 12 +- tools/mailinfo.c | 632 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 556 insertions(+), 88 deletions(-) diff --git a/tools/git-applymbox b/tools/git-applymbox index afcb00a3f5..2b32dab5f5 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/ | 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 a36123a1f5..ef2add7ace 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 #include #include #include +#include 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 " + * 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; } -static void check_line(char *line, int len) +/* 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) { - 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); + 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 char * cleanup_subject(char *subject) +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 int is_multipart_boundary(const char *line) +{ + return (!memcmp(line, multipart_boundary, multipart_boundary_len)); +} + +static int eatspace(char *line) +{ + int len = strlen(line); + while (len > 0 && isspace(line[len-1])) + line[--len] = 0; + return len; +} + +#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,69 +286,53 @@ 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) { - FILE *out = cmitmsg; - char *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); + int i; - do { - if (!memcmp("diff -", line, 6) || - !memcmp("---", line, 3) || - !memcmp("Index: ", line, 7)) - out = patchfile; - - fputs(line, out); - } while (fgets(line, sizeof(line), stdin) != NULL); - - if (out == cmitmsg) { - fprintf(stderr, "No patch found\n"); - exit(1); + 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; + } } - - fclose(cmitmsg); - fclose(patchfile); } -static int eatspace(char *line) +static void check_subheader_line(char *line, int len) { - int len = strlen(line); - while (len > 0 && isspace(line[len-1])) - line[--len] = 0; - return 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 handle_body(void) +static void check_header_line(char *line, int len) { - int has_from = 0; - int has_date = 0; - - /* First lines of body can have From: and Date: */ - while (fgets(line, sizeof(line), stdin) != NULL) { - int len = eatspace(line); - 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; - } - } - line[len] = '\n'; - handle_rest(); - break; - } + 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) @@ -239,23 +356,365 @@ static int read_one_header_line(char *line, int sz, FILE *in) return ofs; } -static void usage(void) +static unsigned hexval(int c) { - fprintf(stderr, "mailinfo msg-file patch-file < email\n"); - exit(1); + 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) +{ + 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); + + /* 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)) + 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; + } + + /* 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); + fclose(cmitmsg); + cmitmsg = NULL; + return 0; +} + +/* We have done the commit message and have the first + * line of the patch in line[]. + */ +static void handle_patch(void) +{ + 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); +} + +/* 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 seen = 0; + int n = 0; + int len; + + while (fgets(line, sizeof(line), stdin) != NULL) { + again: + len = eatspace(line); + n++; + if (!len) + 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_info(); + if (handle_commit_msg()) + goto again; + handle_patch(); + break; + } + if (n == 0) + return -1; + return 0; +} + +static void handle_multipart_body(void) +{ + 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; + } + 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); + } +} + +/* Non multipart message */ +static void handle_body(void) +{ + 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 info"; -int main(int argc, char ** argv) + "git-mailinfo [-k] [-u] msg patch 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; } From 8e5dd22b297a9a5d89d9639e6665b706ccd34282 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 29 Aug 2005 17:19:47 -0700 Subject: [PATCH 5/7] Updates: show-branch Earlier show-branch gave names only to commits reachable via first parent ancestry chain. Change the naming code to name everybody. The original idea was to stop at the first merge point in the topological order, and --more= to show commits until we show more extra merge points. However depending on the order of how we discover the commits, it additionally showed parents of the th merge points, which was unnecessary. Signed-off-by: Junio C Hamano --- show-branch.c | 130 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 108 insertions(+), 22 deletions(-) diff --git a/show-branch.c b/show-branch.c index 2a4e1768ad..958a5e884a 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; } From d5acdcf18e255308f5f16ae9e7fe48bf21ff68c0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 29 Aug 2005 10:29:53 -0700 Subject: [PATCH 6/7] git-repack-script: Add option to repack all objects. This originally came from Frank Sorenson, but with a bit of rework to allow future enhancements without changing the external interface for pack pruning part. With the '-a' option, all objects in the current repository are packed into a single pack. When the '-d' option is given at the same time, existing packs that were made redundant by this round of repacking are deleted. Since we currently have only two repacking strategies, one with '-a' (everything into one) and the other without '-a' (incrementally pack only the unpacked ones), the '-d' option is meaningful only when used with '-a'; it removes the packs existed before we did the "everything into one" repacking. At least for now. Signed-off-by: Junio C Hamano Acked-by: Frank Sorenson (cherry picked from bfed505327e31221d8de796b3af880bad696b149 commit) --- git-repack-script | 51 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/git-repack-script b/git-repack-script index 1c9a6315dd..80628b538b 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 ;; From f85a41916163097c3c307ac45d1a935564c52bf0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Mon, 29 Aug 2005 17:21:06 -0700 Subject: [PATCH 7/7] Accumulated documentation updates. Signed-off-by: Junio C Hamano --- Documentation/git-applymbox.txt | 54 +++++- Documentation/git-applypatch.txt | 22 ++- Documentation/git-bisect-script.txt | 62 ++++++- Documentation/git-mailinfo.txt | 43 ++++- Documentation/git-prune-script.txt | 22 ++- Documentation/git-repack-script.txt | 33 +++- Documentation/git-show-branch.txt | 5 +- Documentation/git.txt | 245 ++++++++++++++-------------- 8 files changed, 317 insertions(+), 169 deletions(-) diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt index acf0bf3d63..3d813ec4cf 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 ] ... +'git-applymbox' [-u] [-k] [-q] ( -c .dotest/ | ) [ ] 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. -...:: - 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/:: + 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/' + flag to restart the process after inspecting and fixing + them. + +:: + 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. + +:: + 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 55facd2c7a..14ce53aade 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 ] ... +'git-applypatch' [] DESCRIPTION ----------- -Does something not yet documented. +Takes three files , , and 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. +:: + Commit log message (sans the first line, which comes + from e-mail Subject stored in ). -...:: - Some argument not yet documented. +:: + The patch to apply. + +: + 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 1f5e38626b..1b0345199c 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 ] ... +'git bisect' start +'git bisect' bad +'git bisect' good +'git bisect' reset [] 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 +------------------------------------------------ -...:: - 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-mailinfo.txt b/Documentation/git-mailinfo.txt index b75f61c84c..eb3a3d1be7 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 ] ... +'git-mailinfo' [-k] [-u] + DESCRIPTION ----------- -Does something not yet documented. +Reading a single e-mail message from the standard input, and +writes the commit log message in file, and the patches in + 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. -...:: - Some argument not yet documented. +-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. + +:: + The commit log message extracted from e-mail, usually + except the title line which comes from e-mail Subject. + +:: + The patch extracted from e-mail. Author ------ -Written by Linus Torvalds +Written by Linus Torvalds and +Junio C Hamano + Documentation -------------- diff --git a/Documentation/git-prune-script.txt b/Documentation/git-prune-script.txt index e8305a6f3d..0f76e8fd0d 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 497d0dfe80..cd449bcc2d 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-show-branch.txt b/Documentation/git-show-branch.txt index 71d5ba8fd7..5103c0c4f9 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=:: 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 commits beyond that. + flag tells the command to go 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 221889bc75..befe3e52cd 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-read-tree.html[git-read-tree]:: - Reads tree information into the directory cache - -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. +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-write-tree.html[git-write-tree]:: + Creates a tree from the current cache 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,173 +154,171 @@ 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-send-pack.html[git-send-pack]:: + Pushes to a remote repository, intelligently. -link:git-fetch-pack.html[git-fetch-pack]:: - Updates from a remote repository. +link:git-ssh-pull.html[git-ssh-pull]:: + Pulls from a remote repository over ssh connection -link:git-peek-remote.html[git-peek-remote]:: - Lists references on a remote repository using upload-pack protocol. - -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-cherry-pick-script.html[git-cherry-pick-script]:: - Cherry-pick the effect of 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-cherry-pick-script.html[git-cherry-pick-script]:: + Cherry-pick the effect of an existing commit. -link:git-log-script.html[git-log-script]:: - Shows commit logs. - -link:git-shortlog.html[git-shortlog]:: - Summarizes 'git log' output. - -link:git-status-script.html[git-status-script]:: - Shows the working tree status. - -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-resolve-script.html[git-resolve-script]:: - Merge two commits. - -link:git-octopus-script.html[git-octopus-script]:: - Merge more than two commits. - -link:git-push-script.html[git-push-script]:: - Update remote refs along with associated objects. +link:git-clone-script.html[git-clone-script]:: + Clones a repository into a new directory. link:git-commit-script.html[git-commit-script]:: Record changes to the repository. -link:git-show-branch.html[git-show-branch]:: - Show branches and their commits. +link:git-fetch-script.html[git-fetch-script]:: + Download from a remote repository via various protocols. -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-log-script.html[git-log-script]:: + Shows commit logs. 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-rebase-script.html[git-rebase-script]:: + Rebase local commits to new upstream head. + +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-resolve-script.html[git-resolve-script]:: + Merge two commits. + +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-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-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-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-send-email-script.html[git-send-email]:: - Send patch e-mails out of "format-patch --mbox" output. +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 -Commands not yet documented ---------------------------- -link:git-applymbox.html[git-applymbox]:: - git-applymbox. +Interrogators: -link:git-applypatch.html[git-applypatch]:: - git-applypatch. +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-diff-helper.html[git-diff-helper]:: + Generates patch format output for git-diff-* link:git-mailinfo.html[git-mailinfo]:: - git-mailinfo. + Extracts patch from a single e-mail message. link:git-mailsplit.html[git-mailsplit]:: git-mailsplit. -link:git-bisect-script.html[git-bisect-script]:: - git-bisect-script. +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. + + +Commands not yet documented +--------------------------- link:git-build-rev-cache.html[git-build-rev-cache]:: git-build-rev-cache.