summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rwxr-xr-xcontrib/ciabot/ciabot.py2
-rwxr-xr-xcontrib/completion/git-completion.bash675
-rw-r--r--contrib/emacs/git-blame.el1
-rw-r--r--contrib/emacs/git.el13
-rw-r--r--contrib/examples/builtin-fetch--tool.c2
-rwxr-xr-xcontrib/examples/git-merge.sh115
-rwxr-xr-xcontrib/examples/git-revert.sh14
-rwxr-xr-xcontrib/examples/git-svnimport.perl4
-rwxr-xr-xcontrib/fast-import/git-p4469
-rw-r--r--contrib/fast-import/git-p4.txt41
-rwxr-xr-xcontrib/fast-import/import-directories.perl3
-rw-r--r--contrib/git-shell-commands/README18
-rwxr-xr-xcontrib/git-shell-commands/help18
-rwxr-xr-xcontrib/git-shell-commands/list10
-rwxr-xr-xcontrib/hooks/post-receive-email83
-rw-r--r--contrib/svn-fe/svn-fe.c4
-rw-r--r--contrib/svn-fe/svn-fe.txt22
-rwxr-xr-xcontrib/thunderbird-patch-inline/appp.sh4
-rwxr-xr-xcontrib/workdir/git-new-workdir2
19 files changed, 1155 insertions, 345 deletions
diff --git a/contrib/ciabot/ciabot.py b/contrib/ciabot/ciabot.py
index d0627e0852..9775dffb5d 100755
--- a/contrib/ciabot/ciabot.py
+++ b/contrib/ciabot/ciabot.py
@@ -122,7 +122,7 @@ def report(refname, merged):
branch = os.path.basename(refname)
# Compute a shortnane for the revision
- rev = do("git describe ${merged} 2>/dev/null") or merged[:12]
+ rev = do("git describe '"+ merged +"' 2>/dev/null") or merged[:12]
# Extract the neta-information for the commit
rawcommit = do("git cat-file commit " + merged)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 82e6609689..5a8309076d 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1,6 +1,6 @@
#!bash
#
-# bash completion support for core Git.
+# bash/zsh completion support for core Git.
#
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
# Conceptually based on gitcompletion (http://gitweb.hawaga.org.uk/).
@@ -18,11 +18,12 @@
# To use these routines:
#
# 1) Copy this file to somewhere (e.g. ~/.git-completion.sh).
-# 2) Added the following line to your .bashrc:
+# 2) Add the following line to your .bashrc/.zshrc:
# source ~/.git-completion.sh
#
# 3) Consider changing your PS1 to also show the current branch:
-# PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+# Bash: PS1='[\u@\h \W$(__git_ps1 " (%s)")]\$ '
+# ZSH: PS1='[%n@%m %c$(__git_ps1 " (%s)")]\$ '
#
# The argument to __git_ps1 will be displayed only if you
# are currently in a git repository. The %s token will be
@@ -72,6 +73,10 @@
# git@vger.kernel.org
#
+if [[ -n ${ZSH_VERSION-} ]]; then
+ autoload -U +X bashcompinit && bashcompinit
+fi
+
case "$COMP_WORDBREAKS" in
*:*) : great ;;
*) COMP_WORDBREAKS="$COMP_WORDBREAKS:"
@@ -138,11 +143,12 @@ __git_ps1_show_upstream ()
# get the upstream from the "git-svn-id: ..." in a commit message
# (git-svn uses essentially the same procedure internally)
local svn_upstream=($(git log --first-parent -1 \
- --grep="^git-svn-id: \(${svn_url_pattern:2}\)" 2>/dev/null))
+ --grep="^git-svn-id: \(${svn_url_pattern#??}\)" 2>/dev/null))
if [[ 0 -ne ${#svn_upstream[@]} ]]; then
svn_upstream=${svn_upstream[ ${#svn_upstream[@]} - 2 ]}
svn_upstream=${svn_upstream%@*}
- for ((n=1; "$n" <= "${#svn_remote[@]}"; ++n)); do
+ local n_stop="${#svn_remote[@]}"
+ for ((n=1; n <= n_stop; ++n)); do
svn_upstream=${svn_upstream#${svn_remote[$n]}}
done
@@ -240,6 +246,8 @@ __git_ps1 ()
fi
elif [ -f "$g/MERGE_HEAD" ]; then
r="|MERGING"
+ elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
+ r="|CHERRY-PICKING"
elif [ -f "$g/BISECT_LOG" ]; then
r="|BISECTING"
fi
@@ -321,15 +329,172 @@ __gitcomp_1 ()
done
}
+# The following function is based on code from:
+#
+# bash_completion - programmable completion functions for bash 3.2+
+#
+# Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
+# © 2009-2010, Bash Completion Maintainers
+# <bash-completion-devel@lists.alioth.debian.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+# The latest version of this software can be obtained here:
+#
+# http://bash-completion.alioth.debian.org/
+#
+# RELEASE: 2.x
+
+# This function can be used to access a tokenized list of words
+# on the command line:
+#
+# __git_reassemble_comp_words_by_ref '=:'
+# if test "${words_[cword_-1]}" = -w
+# then
+# ...
+# fi
+#
+# The argument should be a collection of characters from the list of
+# word completion separators (COMP_WORDBREAKS) to treat as ordinary
+# characters.
+#
+# This is roughly equivalent to going back in time and setting
+# COMP_WORDBREAKS to exclude those characters. The intent is to
+# make option types like --date=<type> and <rev>:<path> easy to
+# recognize by treating each shell word as a single token.
+#
+# It is best not to set COMP_WORDBREAKS directly because the value is
+# shared with other completion scripts. By the time the completion
+# function gets called, COMP_WORDS has already been populated so local
+# changes to COMP_WORDBREAKS have no effect.
+#
+# Output: words_, cword_, cur_.
+
+__git_reassemble_comp_words_by_ref()
+{
+ local exclude i j first
+ # Which word separators to exclude?
+ exclude="${1//[^$COMP_WORDBREAKS]}"
+ cword_=$COMP_CWORD
+ if [ -z "$exclude" ]; then
+ words_=("${COMP_WORDS[@]}")
+ return
+ fi
+ # List of word completion separators has shrunk;
+ # re-assemble words to complete.
+ for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
+ # Append each nonempty word consisting of just
+ # word separator characters to the current word.
+ first=t
+ while
+ [ $i -gt 0 ] &&
+ [ -n "${COMP_WORDS[$i]}" ] &&
+ # word consists of excluded word separators
+ [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
+ do
+ # Attach to the previous token,
+ # unless the previous token is the command name.
+ if [ $j -ge 2 ] && [ -n "$first" ]; then
+ ((j--))
+ fi
+ first=
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ if (($i < ${#COMP_WORDS[@]} - 1)); then
+ ((i++))
+ else
+ # Done.
+ return
+ fi
+ done
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ done
+}
+
+if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
+if [[ -z ${ZSH_VERSION:+set} ]]; then
+_get_comp_words_by_ref ()
+{
+ local exclude cur_ words_ cword_
+ if [ "$1" = "-n" ]; then
+ exclude=$2
+ shift 2
+ fi
+ __git_reassemble_comp_words_by_ref "$exclude"
+ cur_=${words_[cword_]}
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=$cur_
+ ;;
+ prev)
+ prev=${words_[$cword_-1]}
+ ;;
+ words)
+ words=("${words_[@]}")
+ ;;
+ cword)
+ cword=$cword_
+ ;;
+ esac
+ shift
+ done
+}
+else
+_get_comp_words_by_ref ()
+{
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=${COMP_WORDS[COMP_CWORD]}
+ ;;
+ prev)
+ prev=${COMP_WORDS[COMP_CWORD-1]}
+ ;;
+ words)
+ words=("${COMP_WORDS[@]}")
+ ;;
+ cword)
+ cword=$COMP_CWORD
+ ;;
+ -n)
+ # assume COMP_WORDBREAKS is already set sanely
+ shift
+ ;;
+ esac
+ shift
+ done
+}
+fi
+fi
+
# __gitcomp accepts 1, 2, 3, or 4 arguments
# generates completion reply with compgen
__gitcomp ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur_="$cur"
+
if [ $# -gt 2 ]; then
- cur="$3"
+ cur_="$3"
fi
- case "$cur" in
+ case "$cur_" in
--*=)
COMPREPLY=()
;;
@@ -337,7 +502,7 @@ __gitcomp ()
local IFS=$'\n'
COMPREPLY=($(compgen -P "${2-}" \
-W "$(__gitcomp_1 "${1-}" "${4-}")" \
- -- "$cur"))
+ -- "$cur_"))
;;
esac
}
@@ -380,16 +545,19 @@ __git_tags ()
done
}
-# __git_refs accepts 0 or 1 arguments (to pass to __gitdir)
+# __git_refs accepts 0, 1 (to pass to __gitdir), or 2 arguments
+# presence of 2nd argument means use the guess heuristic employed
+# by checkout for tracking branches
__git_refs ()
{
- local i is_hash=y dir="$(__gitdir "${1-}")"
- local cur="${COMP_WORDS[COMP_CWORD]}" format refs
+ local i is_hash=y dir="$(__gitdir "${1-}")" track="${2-}"
+ local format refs
if [ -d "$dir" ]; then
case "$cur" in
refs|refs/*)
format="refname"
refs="${cur%/*}"
+ track=""
;;
*)
for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
@@ -401,6 +569,21 @@ __git_refs ()
esac
git --git-dir="$dir" for-each-ref --format="%($format)" \
$refs
+ if [ -n "$track" ]; then
+ # employ the heuristic used by git checkout
+ # Try to find a remote branch that matches the completion word
+ # but only output if the branch name is unique
+ local ref entry
+ git --git-dir="$dir" for-each-ref --shell --format="ref=%(refname:short)" \
+ "refs/remotes/" | \
+ while read entry; do
+ eval "$entry"
+ ref="${ref#*/}"
+ if [[ "$ref" == "$cur"* ]]; then
+ echo "$ref"
+ fi
+ done | uniq -u
+ fi
return
fi
for i in $(git ls-remote "$dir" 2>/dev/null); do
@@ -445,12 +628,12 @@ __git_refs_remotes ()
__git_remotes ()
{
local i ngoff IFS=$'\n' d="$(__gitdir)"
- shopt -q nullglob || ngoff=1
- shopt -s nullglob
+ __git_shopt -q nullglob || ngoff=1
+ __git_shopt -s nullglob
for i in "$d/remotes"/*; do
echo ${i#$d/remotes/}
done
- [ "$ngoff" ] && shopt -u nullglob
+ [ "$ngoff" ] && __git_shopt -u nullglob
for i in $(git --git-dir="$d" config --get-regexp 'remote\..*\.url' 2>/dev/null); do
i="${i#remote.}"
echo "${i/.url*/}"
@@ -480,24 +663,27 @@ __git_compute_merge_strategies ()
: ${__git_merge_strategies:=$(__git_list_merge_strategies)}
}
-__git_complete_file ()
+__git_complete_revlist_file ()
{
- local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
- case "$cur" in
+ local pfx ls ref cur_="$cur"
+ case "$cur_" in
+ *..?*:*)
+ return
+ ;;
?*:*)
- ref="${cur%%:*}"
- cur="${cur#*:}"
- case "$cur" in
+ ref="${cur_%%:*}"
+ cur_="${cur_#*:}"
+ case "$cur_" in
?*/*)
- pfx="${cur%/*}"
- cur="${cur##*/}"
+ pfx="${cur_%/*}"
+ cur_="${cur_##*/}"
ls="$ref:$pfx"
pfx="$pfx/"
;;
*)
ls="$ref"
;;
- esac
+ esac
case "$COMP_WORDBREAKS" in
*:*) : great ;;
@@ -520,27 +706,17 @@ __git_complete_file ()
s,$,/,
}
s/^.* //')" \
- -- "$cur"))
- ;;
- *)
- __gitcomp "$(__git_refs)"
+ -- "$cur_"))
;;
- esac
-}
-
-__git_complete_revlist ()
-{
- local pfx cur="${COMP_WORDS[COMP_CWORD]}"
- case "$cur" in
*...*)
- pfx="${cur%...*}..."
- cur="${cur#*...}"
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ pfx="${cur_%...*}..."
+ cur_="${cur_#*...}"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
;;
*..*)
- pfx="${cur%..*}.."
- cur="${cur#*..}"
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ pfx="${cur_%..*}.."
+ cur_="${cur_#*..}"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
;;
*)
__gitcomp "$(__git_refs)"
@@ -548,13 +724,23 @@ __git_complete_revlist ()
esac
}
+
+__git_complete_file ()
+{
+ __git_complete_revlist_file
+}
+
+__git_complete_revlist ()
+{
+ __git_complete_revlist_file
+}
+
__git_complete_remote_or_refspec ()
{
- local cmd="${COMP_WORDS[1]}"
- local cur="${COMP_WORDS[COMP_CWORD]}"
+ local cur_="$cur" cmd="${words[1]}"
local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
--all)
@@ -581,40 +767,40 @@ __git_complete_remote_or_refspec ()
return
fi
[ "$remote" = "." ] && remote=
- case "$cur" in
+ case "$cur_" in
*:*)
case "$COMP_WORDBREAKS" in
*:*) : great ;;
- *) pfx="${cur%%:*}:" ;;
+ *) pfx="${cur_%%:*}:" ;;
esac
- cur="${cur#*:}"
+ cur_="${cur_#*:}"
lhs=0
;;
+*)
pfx="+"
- cur="${cur#+}"
+ cur_="${cur_#+}"
;;
esac
case "$cmd" in
fetch)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur"
+ __gitcomp "$(__git_refs2 "$remote")" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
fi
;;
pull)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+ __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
fi
;;
push)
if [ $lhs = 1 ]; then
- __gitcomp "$(__git_refs)" "$pfx" "$cur"
+ __gitcomp "$(__git_refs)" "$pfx" "$cur_"
else
- __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur"
+ __gitcomp "$(__git_refs "$remote")" "$pfx" "$cur_"
fi
;;
esac
@@ -623,12 +809,11 @@ __git_complete_remote_or_refspec ()
__git_complete_strategy ()
{
__git_compute_merge_strategies
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
-s|--strategy)
__gitcomp "$__git_merge_strategies"
return 0
esac
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--strategy=*)
__gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
@@ -711,7 +896,6 @@ __git_list_porcelain_commands ()
quiltimport) : import;;
read-tree) : plumbing;;
receive-pack) : plumbing;;
- reflog) : plumbing;;
remote-*) : transport;;
repo-config) : deprecated;;
rerere) : plumbing;;
@@ -750,6 +934,19 @@ __git_compute_porcelain_commands ()
: ${__git_porcelain_commands:=$(__git_list_porcelain_commands)}
}
+__git_pretty_aliases ()
+{
+ local i IFS=$'\n'
+ for i in $(git --git-dir="$(__gitdir)" config --get-regexp "pretty\..*" 2>/dev/null); do
+ case "$i" in
+ pretty.*)
+ i="${i#pretty.}"
+ echo "${i/ */}"
+ ;;
+ esac
+ done
+}
+
__git_aliases ()
{
local i IFS=$'\n'
@@ -789,9 +986,8 @@ __git_aliased_command ()
__git_find_on_cmdline ()
{
local word subcommand c=1
-
- while [ $c -lt $COMP_CWORD ]; do
- word="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ word="${words[c]}"
for subcommand in $1; do
if [ "$subcommand" = "$word" ]; then
echo "$subcommand"
@@ -805,8 +1001,8 @@ __git_find_on_cmdline ()
__git_has_doubledash ()
{
local c=1
- while [ $c -lt $COMP_CWORD ]; do
- if [ "--" = "${COMP_WORDS[c]}" ]; then
+ while [ $c -lt $cword ]; do
+ if [ "--" = "${words[c]}" ]; then
return 0
fi
c=$((++c))
@@ -818,7 +1014,7 @@ __git_whitespacelist="nowarn warn error error-all fix"
_git_am ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ local dir="$(__gitdir)"
if [ -d "$dir"/rebase-apply ]; then
__gitcomp "--skip --continue --resolved --abort"
return
@@ -842,7 +1038,6 @@ _git_am ()
_git_apply ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--whitespace=*)
__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
@@ -865,7 +1060,6 @@ _git_add ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -879,7 +1073,6 @@ _git_add ()
_git_archive ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--format=*)
__gitcomp "$(git archive --list)" "" "${cur##--format=}"
@@ -907,12 +1100,16 @@ _git_bisect ()
local subcommands="start bad good skip reset visualize replay log run"
local subcommand="$(__git_find_on_cmdline "$subcommands")"
if [ -z "$subcommand" ]; then
- __gitcomp "$subcommands"
+ if [ -f "$(__gitdir)"/BISECT_START ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "replay start"
+ fi
return
fi
case "$subcommand" in
- bad|good|reset|skip)
+ bad|good|reset|skip|start)
__gitcomp "$(__git_refs)"
;;
*)
@@ -925,8 +1122,8 @@ _git_branch ()
{
local i c=1 only_local_ref="n" has_r="n"
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
-d|-m) only_local_ref="y" ;;
-r) has_r="y" ;;
@@ -934,7 +1131,7 @@ _git_branch ()
c=$((++c))
done
- case "${COMP_WORDS[COMP_CWORD]}" in
+ case "$cur" in
--*)
__gitcomp "
--color --no-color --verbose --abbrev= --no-abbrev
@@ -954,8 +1151,8 @@ _git_branch ()
_git_bundle ()
{
- local cmd="${COMP_WORDS[2]}"
- case "$COMP_CWORD" in
+ local cmd="${words[2]}"
+ case "$cword" in
2)
__gitcomp "create list-heads verify unbundle"
;;
@@ -976,7 +1173,6 @@ _git_checkout ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--conflict=*)
__gitcomp "diff3 merge" "" "${cur##--conflict=}"
@@ -988,7 +1184,13 @@ _git_checkout ()
"
;;
*)
- __gitcomp "$(__git_refs)"
+ # check if --track, --no-track, or --no-guess was specified
+ # if so, disable DWIM mode
+ local flags="--track --no-track --no-guess" track=1
+ if [ -n "$(__git_find_on_cmdline "$flags")" ]; then
+ track=''
+ fi
+ __gitcomp "$(__git_refs '' $track)"
;;
esac
}
@@ -1000,7 +1202,6 @@ _git_cherry ()
_git_cherry_pick ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--edit --no-commit"
@@ -1015,7 +1216,6 @@ _git_clean ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--dry-run --quiet"
@@ -1027,7 +1227,6 @@ _git_clean ()
_git_clone ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1054,7 +1253,6 @@ _git_commit ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--cleanup=*)
__gitcomp "default strip verbatim whitespace
@@ -1089,7 +1287,6 @@ _git_commit ()
_git_describe ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1121,28 +1318,26 @@ _git_diff ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
- --base --ours --theirs
+ --base --ours --theirs --no-index
$__git_diff_common_options
"
return
;;
esac
- __git_complete_file
+ __git_complete_revlist_file
}
__git_mergetools_common="diffuse ecmerge emerge kdiff3 meld opendiff
- tkdiff vimdiff gvimdiff xxdiff araxis p4merge
+ tkdiff vimdiff gvimdiff xxdiff araxis p4merge bc3
"
_git_difftool ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}"
@@ -1167,7 +1362,6 @@ __git_fetch_options="
_git_fetch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "$__git_fetch_options"
@@ -1179,7 +1373,6 @@ _git_fetch ()
_git_format_patch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--thread=*)
__gitcomp "
@@ -1211,7 +1404,6 @@ _git_format_patch ()
_git_fsck ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1226,7 +1418,6 @@ _git_fsck ()
_git_gc ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--prune --aggressive"
@@ -1245,14 +1436,14 @@ _git_grep ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
--cached
--text --ignore-case --word-regexp --invert-match
- --full-name
+ --full-name --line-number
--extended-regexp --basic-regexp --fixed-strings
+ --perl-regexp
--files-with-matches --name-only
--files-without-match
--max-depth
@@ -1268,7 +1459,6 @@ _git_grep ()
_git_help ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--all --info --man --web"
@@ -1276,7 +1466,7 @@ _git_help ()
;;
esac
__git_compute_all_commands
- __gitcomp "$__git_all_commands
+ __gitcomp "$__git_all_commands $(__git_aliases)
attributes cli core-tutorial cvs-migration
diffcore gitk glossary hooks ignore modules
repository-layout tutorial tutorial-2
@@ -1286,7 +1476,6 @@ _git_help ()
_git_init ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--shared=*)
__gitcomp "
@@ -1306,7 +1495,6 @@ _git_ls_files ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--cached --deleted --modified --others --ignored
@@ -1340,12 +1528,14 @@ __git_log_common_options="
--max-count=
--max-age= --since= --after=
--min-age= --until= --before=
+ --min-parents= --max-parents=
+ --no-min-parents --no-max-parents
"
# Options that go well for log and gitk (not shortlog)
__git_log_gitk_options="
--dense --sparse --full-history
--simplify-merges --simplify-by-decoration
- --left-right
+ --left-right --notes --no-notes
"
# Options that go well for log and shortlog (not gitk)
__git_log_shortlog_options="
@@ -1360,7 +1550,6 @@ _git_log ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
local g="$(git rev-parse --git-dir 2>/dev/null)"
local merge=""
if [ -f "$g/MERGE_HEAD" ]; then
@@ -1368,12 +1557,12 @@ _git_log ()
fi
case "$cur" in
--pretty=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--pretty=}"
return
;;
--format=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--format=}"
return
;;
@@ -1419,7 +1608,6 @@ _git_merge ()
{
__git_complete_strategy && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "$__git_merge_options"
@@ -1430,7 +1618,6 @@ _git_merge ()
_git_mergetool ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--tool=*)
__gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}"
@@ -1451,7 +1638,6 @@ _git_merge_base ()
_git_mv ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--dry-run"
@@ -1468,18 +1654,49 @@ _git_name_rev ()
_git_notes ()
{
- local subcommands="edit show"
- if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
- __gitcomp "$subcommands"
- return
- fi
+ local subcommands='add append copy edit list prune remove show'
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
- case "${COMP_WORDS[COMP_CWORD-1]}" in
- -m|-F)
- COMPREPLY=()
+ case "$subcommand,$cur" in
+ ,--*)
+ __gitcomp '--ref'
+ ;;
+ ,*)
+ case "${words[cword-1]}" in
+ --ref)
+ __gitcomp "$(__git_refs)"
+ ;;
+ *)
+ __gitcomp "$subcommands --ref"
+ ;;
+ esac
+ ;;
+ add,--reuse-message=*|append,--reuse-message=*)
+ __gitcomp "$(__git_refs)" "" "${cur##--reuse-message=}"
+ ;;
+ add,--reedit-message=*|append,--reedit-message=*)
+ __gitcomp "$(__git_refs)" "" "${cur##--reedit-message=}"
+ ;;
+ add,--*|append,--*)
+ __gitcomp '--file= --message= --reedit-message=
+ --reuse-message='
+ ;;
+ copy,--*)
+ __gitcomp '--stdin'
+ ;;
+ prune,--*)
+ __gitcomp '--dry-run --verbose'
+ ;;
+ prune,*)
;;
*)
- __gitcomp "$(__git_refs)"
+ case "${words[cword-1]}" in
+ -m|-F)
+ ;;
+ *)
+ __gitcomp "$(__git_refs)"
+ ;;
+ esac
;;
esac
}
@@ -1488,7 +1705,6 @@ _git_pull ()
{
__git_complete_strategy && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -1504,8 +1720,7 @@ _git_pull ()
_git_push ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
--repo)
__gitcomp "$(__git_remotes)"
return
@@ -1528,7 +1743,7 @@ _git_push ()
_git_rebase ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+ local dir="$(__gitdir)"
if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
__gitcomp "--continue --skip --abort"
return
@@ -1553,12 +1768,23 @@ _git_rebase ()
__gitcomp "$(__git_refs)"
}
+_git_reflog ()
+{
+ local subcommands="show delete expire"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ else
+ __gitcomp "$(__git_refs)"
+ fi
+}
+
__git_send_email_confirm_options="always never auto cc compose"
__git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
_git_send_email ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--confirm=*)
__gitcomp "
@@ -1600,9 +1826,9 @@ _git_stage ()
__git_config_get_set_variables ()
{
- local prevword word config_file= c=$COMP_CWORD
+ local prevword word config_file= c=$cword
while [ $c -gt 1 ]; do
- word="${COMP_WORDS[c]}"
+ word="${words[c]}"
case "$word" in
--global|--system|--file=*)
config_file="$word"
@@ -1630,9 +1856,7 @@ __git_config_get_set_variables ()
_git_config ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
- local prv="${COMP_WORDS[COMP_CWORD-1]}"
- case "$prv" in
+ case "$prev" in
branch.*.remote)
__gitcomp "$(__git_remotes)"
return
@@ -1642,13 +1866,13 @@ _git_config ()
return
;;
remote.*.fetch)
- local remote="${prv#remote.}"
+ local remote="${prev#remote.}"
remote="${remote%.fetch}"
__gitcomp "$(__git_refs_remotes "$remote")"
return
;;
remote.*.push)
- local remote="${prv#remote.}"
+ local remote="${prev#remote.}"
remote="${remote%.push}"
__gitcomp "$(git --git-dir="$(__gitdir)" \
for-each-ref --format='%(refname):%(refname)' \
@@ -1717,98 +1941,108 @@ _git_config ()
return
;;
branch.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "remote merge mergeoptions rebase" "$pfx" "$cur_"
return
;;
branch.*)
- local pfx="${cur%.*}."
- cur="${cur#*.}"
- __gitcomp "$(__git_heads)" "$pfx" "$cur" "."
+ local pfx="${cur%.*}." cur_="${cur#*.}"
+ __gitcomp "$(__git_heads)" "$pfx" "$cur_" "."
return
;;
guitool.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
__gitcomp "
argprompt cmd confirm needsfile noconsole norescan
prompt revprompt revunmerged title
- " "$pfx" "$cur"
+ " "$pfx" "$cur_"
return
;;
difftool.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "cmd path" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "cmd path" "$pfx" "$cur_"
return
;;
man.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "cmd path" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "cmd path" "$pfx" "$cur_"
return
;;
mergetool.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "cmd path trustExitCode" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "cmd path trustExitCode" "$pfx" "$cur_"
return
;;
pager.*)
- local pfx="${cur%.*}."
- cur="${cur#*.}"
+ local pfx="${cur%.*}." cur_="${cur#*.}"
__git_compute_all_commands
- __gitcomp "$__git_all_commands" "$pfx" "$cur"
+ __gitcomp "$__git_all_commands" "$pfx" "$cur_"
return
;;
remote.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
__gitcomp "
url proxy fetch push mirror skipDefaultUpdate
receivepack uploadpack tagopt pushurl
- " "$pfx" "$cur"
+ " "$pfx" "$cur_"
return
;;
remote.*)
- local pfx="${cur%.*}."
- cur="${cur#*.}"
- __gitcomp "$(__git_remotes)" "$pfx" "$cur" "."
+ local pfx="${cur%.*}." cur_="${cur#*.}"
+ __gitcomp "$(__git_remotes)" "$pfx" "$cur_" "."
return
;;
url.*.*)
- local pfx="${cur%.*}."
- cur="${cur##*.}"
- __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur"
+ local pfx="${cur%.*}." cur_="${cur##*.}"
+ __gitcomp "insteadOf pushInsteadOf" "$pfx" "$cur_"
return
;;
esac
__gitcomp "
- add.ignore-errors
+ add.ignoreErrors
+ advice.commitBeforeMerge
+ advice.detachedHead
+ advice.implicitIdentity
+ advice.pushNonFastForward
+ advice.resolveConflict
+ advice.statusHints
alias.
+ am.keepcr
apply.ignorewhitespace
apply.whitespace
branch.autosetupmerge
branch.autosetuprebase
+ browser.
clean.requireForce
color.branch
color.branch.current
color.branch.local
color.branch.plain
color.branch.remote
+ color.decorate.HEAD
+ color.decorate.branch
+ color.decorate.remoteBranch
+ color.decorate.stash
+ color.decorate.tag
color.diff
color.diff.commit
color.diff.frag
+ color.diff.func
color.diff.meta
color.diff.new
color.diff.old
color.diff.plain
color.diff.whitespace
color.grep
- color.grep.external
+ color.grep.context
+ color.grep.filename
+ color.grep.function
+ color.grep.linenumber
color.grep.match
+ color.grep.selected
+ color.grep.separator
color.interactive
+ color.interactive.error
color.interactive.header
color.interactive.help
color.interactive.prompt
@@ -1822,21 +2056,29 @@ _git_config ()
color.status.untracked
color.status.updated
color.ui
+ commit.status
commit.template
+ core.abbrev
+ core.askpass
+ core.attributesfile
core.autocrlf
core.bare
+ core.bigFileThreshold
core.compression
core.createObject
core.deltaBaseCacheLimit
core.editor
+ core.eol
core.excludesfile
core.fileMode
core.fsyncobjectfiles
core.gitProxy
core.ignoreCygwinFSTricks
core.ignoreStat
+ core.ignorecase
core.logAllRefUpdates
core.loosecompression
+ core.notesRef
core.packedGitLimit
core.packedGitWindowSize
core.pager
@@ -1846,6 +2088,7 @@ _git_config ()
core.repositoryFormatVersion
core.safecrlf
core.sharedRepository
+ core.sparseCheckout
core.symlinks
core.trustctime
core.warnAmbiguousRefs
@@ -1853,15 +2096,17 @@ _git_config ()
core.worktree
diff.autorefreshindex
diff.external
+ diff.ignoreSubmodules
diff.mnemonicprefix
+ diff.noprefix
diff.renameLimit
- diff.renameLimit.
diff.renames
diff.suppressBlankEmpty
diff.tool
diff.wordRegex
difftool.
difftool.prompt
+ fetch.recurseSubmodules
fetch.unpackLimit
format.attach
format.cc
@@ -1873,6 +2118,8 @@ _git_config ()
format.subjectprefix
format.suffix
format.thread
+ format.to
+ gc.
gc.aggressiveWindow
gc.auto
gc.autopacklimit
@@ -1910,15 +2157,20 @@ _git_config ()
http.lowSpeedLimit
http.lowSpeedTime
http.maxRequests
+ http.minSessions
http.noEPSV
+ http.postBuffer
http.proxy
http.sslCAInfo
http.sslCAPath
http.sslCert
+ http.sslCertPasswordProtected
http.sslKey
http.sslVerify
+ http.useragent
i18n.commitEncoding
i18n.logOutputEncoding
+ imap.authMethod
imap.folder
imap.host
imap.pass
@@ -1927,6 +2179,7 @@ _git_config ()
imap.sslverify
imap.tunnel
imap.user
+ init.templatedir
instaweb.browser
instaweb.httpd
instaweb.local
@@ -1934,19 +2187,29 @@ _git_config ()
instaweb.port
interactive.singlekey
log.date
+ log.decorate
log.showroot
mailmap.file
man.
man.viewer
+ merge.
merge.conflictstyle
merge.log
merge.renameLimit
+ merge.renormalize
merge.stat
merge.tool
merge.verbosity
mergetool.
mergetool.keepBackup
+ mergetool.keepTemporaries
mergetool.prompt
+ notes.displayRef
+ notes.rewrite.
+ notes.rewrite.amend
+ notes.rewrite.rebase
+ notes.rewriteMode
+ notes.rewriteRef
pack.compression
pack.deltaCacheLimit
pack.deltaCacheSize
@@ -1957,31 +2220,42 @@ _git_config ()
pack.window
pack.windowMemory
pager.
+ pretty.
pull.octopus
pull.twohead
push.default
+ rebase.autosquash
rebase.stat
+ receive.autogc
receive.denyCurrentBranch
+ receive.denyDeleteCurrent
receive.denyDeletes
receive.denyNonFastForwards
receive.fsckObjects
receive.unpackLimit
+ receive.updateserverinfo
+ remotes.
repack.usedeltabaseoffset
rerere.autoupdate
rerere.enabled
+ sendemail.
sendemail.aliasesfile
- sendemail.aliasesfiletype
+ sendemail.aliasfiletype
sendemail.bcc
sendemail.cc
sendemail.cccmd
sendemail.chainreplyto
sendemail.confirm
sendemail.envelopesender
+ sendemail.from
+ sendemail.identity
sendemail.multiedit
sendemail.signedoffbycc
+ sendemail.smtpdomain
sendemail.smtpencryption
sendemail.smtppass
sendemail.smtpserver
+ sendemail.smtpserveroption
sendemail.smtpserverport
sendemail.smtpuser
sendemail.suppresscc
@@ -1992,6 +2266,8 @@ _git_config ()
showbranch.default
status.relativePaths
status.showUntrackedFiles
+ status.submodulesummary
+ submodule.
tar.umask
transfer.unpackLimit
url.
@@ -2039,7 +2315,6 @@ _git_reset ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--merge --mixed --hard --soft --patch"
@@ -2051,7 +2326,6 @@ _git_reset ()
_git_revert ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--edit --mainline --no-edit --no-commit --signoff"
@@ -2065,7 +2339,6 @@ _git_rm ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--cached --dry-run --ignore-unmatch --quiet"
@@ -2079,7 +2352,6 @@ _git_shortlog ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -2097,15 +2369,14 @@ _git_show ()
{
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--pretty=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--pretty=}"
return
;;
--format=*)
- __gitcomp "$__git_log_pretty_formats
+ __gitcomp "$__git_log_pretty_formats $(__git_pretty_aliases)
" "" "${cur##--format=}"
return
;;
@@ -2121,7 +2392,6 @@ _git_show ()
_git_show_branch ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "
@@ -2138,7 +2408,6 @@ _git_show_branch ()
_git_stash ()
{
- local cur="${COMP_WORDS[COMP_CWORD]}"
local save_opts='--keep-index --no-keep-index --quiet --patch'
local subcommands='save list show apply clear drop pop create branch'
local subcommand="$(__git_find_on_cmdline "$subcommands")"
@@ -2183,7 +2452,6 @@ _git_submodule ()
local subcommands="add status init update summary foreach sync"
if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$cur" in
--*)
__gitcomp "--quiet --cached"
@@ -2227,7 +2495,6 @@ _git_svn ()
--edit --rmdir --find-copies-harder --copy-similarity=
"
- local cur="${COMP_WORDS[COMP_CWORD]}"
case "$subcommand,$cur" in
fetch,--*)
__gitcomp "--revision= --fetch-all $fc_opts"
@@ -2299,8 +2566,8 @@ _git_svn ()
_git_tag ()
{
local i c=1 f=0
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
-d|-v)
__gitcomp "$(__git_tags)"
@@ -2313,7 +2580,7 @@ _git_tag ()
c=$((++c))
done
- case "${COMP_WORDS[COMP_CWORD-1]}" in
+ case "$prev" in
-m|-F)
COMPREPLY=()
;;
@@ -2339,8 +2606,19 @@ _git ()
{
local i c=1 command __git_dir
- while [ $c -lt $COMP_CWORD ]; do
- i="${COMP_WORDS[c]}"
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
+ fi
+
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+ while [ $c -lt $cword ]; do
+ i="${words[c]}"
case "$i" in
--git-dir=*) __git_dir="${i#--git-dir=}" ;;
--bare) __git_dir="." ;;
@@ -2352,7 +2630,7 @@ _git ()
done
if [ -z "$command" ]; then
- case "${COMP_WORDS[COMP_CWORD]}" in
+ case "$cur" in
--*) __gitcomp "
--paginate
--no-pager
@@ -2372,20 +2650,31 @@ _git ()
fi
local completion_func="_git_${command//-/_}"
- declare -F $completion_func >/dev/null && $completion_func && return
+ declare -f $completion_func >/dev/null && $completion_func && return
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
completion_func="_git_${expansion//-/_}"
- declare -F $completion_func >/dev/null && $completion_func
+ declare -f $completion_func >/dev/null && $completion_func
fi
}
_gitk ()
{
+ if [[ -n ${ZSH_VERSION-} ]]; then
+ emulate -L bash
+ setopt KSH_TYPESET
+
+ # workaround zsh's bug that leaves 'words' as a special
+ # variable in versions < 4.3.12
+ typeset -h words
+ fi
+
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+
__git_has_doubledash && return
- local cur="${COMP_WORDS[COMP_CWORD]}"
local g="$(__gitdir)"
local merge=""
if [ -f "$g/MERGE_HEAD" ]; then
@@ -2417,3 +2706,33 @@ if [ Cygwin = "$(uname -o 2>/dev/null)" ]; then
complete -o bashdefault -o default -o nospace -F _git git.exe 2>/dev/null \
|| complete -o default -o nospace -F _git git.exe
fi
+
+if [[ -n ${ZSH_VERSION-} ]]; then
+ __git_shopt () {
+ local option
+ if [ $# -ne 2 ]; then
+ echo "USAGE: $0 (-q|-s|-u) <option>" >&2
+ return 1
+ fi
+ case "$2" in
+ nullglob)
+ option="$2"
+ ;;
+ *)
+ echo "$0: invalid option: $2" >&2
+ return 1
+ esac
+ case "$1" in
+ -q) setopt | grep -q "$option" ;;
+ -u) unsetopt "$option" ;;
+ -s) setopt "$option" ;;
+ *)
+ echo "$0: invalid flag: $1" >&2
+ return 1
+ esac
+ }
+else
+ __git_shopt () {
+ shopt "$@"
+ }
+fi
diff --git a/contrib/emacs/git-blame.el b/contrib/emacs/git-blame.el
index 7f4c792978..d351cfb6e7 100644
--- a/contrib/emacs/git-blame.el
+++ b/contrib/emacs/git-blame.el
@@ -79,6 +79,7 @@
;;; Code:
(eval-when-compile (require 'cl)) ; to use `push', `pop'
+(require 'format-spec)
(defface git-blame-prefix-face
'((((background dark)) (:foreground "gray"
diff --git a/contrib/emacs/git.el b/contrib/emacs/git.el
index 214930a021..65c95d9d5a 100644
--- a/contrib/emacs/git.el
+++ b/contrib/emacs/git.el
@@ -1310,6 +1310,13 @@ The FILES list must be sorted."
(when sign-off (git-append-sign-off committer-name committer-email)))
buffer))
+(define-derived-mode git-log-edit-mode log-edit-mode "Git-Log-Edit"
+ "Major mode for editing git log messages.
+
+Set up git-specific `font-lock-keywords' for `log-edit-mode'."
+ (set (make-local-variable 'font-lock-defaults)
+ '(git-log-edit-font-lock-keywords t t)))
+
(defun git-commit-file ()
"Commit the marked file(s), asking for a commit message."
(interactive)
@@ -1335,9 +1342,9 @@ The FILES list must be sorted."
(git-setup-log-buffer buffer (git-get-merge-heads) author-name author-email subject date))
(if (boundp 'log-edit-diff-function)
(log-edit 'git-do-commit nil '((log-edit-listfun . git-log-edit-files)
- (log-edit-diff-function . git-log-edit-diff)) buffer)
- (log-edit 'git-do-commit nil 'git-log-edit-files buffer))
- (setq font-lock-keywords (font-lock-compile-keywords git-log-edit-font-lock-keywords))
+ (log-edit-diff-function . git-log-edit-diff)) buffer 'git-log-edit-mode)
+ (log-edit 'git-do-commit nil 'git-log-edit-files buffer
+ 'git-log-edit-mode))
(setq paragraph-separate (concat (regexp-quote git-log-msg-separator) "$\\|Author: \\|Date: \\|Merge: \\|Signed-off-by: \\|\f\\|[ ]*$"))
(setq buffer-file-coding-system coding-system)
(re-search-forward (regexp-quote (concat git-log-msg-separator "\n")) nil t))))
diff --git a/contrib/examples/builtin-fetch--tool.c b/contrib/examples/builtin-fetch--tool.c
index cd10dbcbc9..3140e405fa 100644
--- a/contrib/examples/builtin-fetch--tool.c
+++ b/contrib/examples/builtin-fetch--tool.c
@@ -148,7 +148,7 @@ static int append_fetch_head(FILE *fp,
what = remote_name + 10;
}
else if (!strncmp(remote_name, "refs/remotes/", 13)) {
- kind = "remote branch";
+ kind = "remote-tracking branch";
what = remote_name + 13;
}
else {
diff --git a/contrib/examples/git-merge.sh b/contrib/examples/git-merge.sh
index 8f617fcb70..7b922c3948 100755
--- a/contrib/examples/git-merge.sh
+++ b/contrib/examples/git-merge.sh
@@ -15,7 +15,10 @@ log add list of one-line log to merge commit message
squash create a single commit instead of doing a merge
commit perform a commit if the merge succeeds (default)
ff allow fast-forward (default)
+ff-only abort if fast-forward is not possible
+rerere-autoupdate update index with any reused conflict resolution
s,strategy= merge strategy to use
+X= option for selected merge strategy
m,message= message to be used for the merge commit (if any)
"
@@ -25,26 +28,32 @@ require_work_tree
cd_to_toplevel
test -z "$(git ls-files -u)" ||
- die "You are in the middle of a conflicted merge."
+ die "Merge is not possible because you have unmerged files."
+
+! test -e "$GIT_DIR/MERGE_HEAD" ||
+ die 'You have not concluded your merge (MERGE_HEAD exists).'
LF='
'
all_strategies='recur recursive octopus resolve stupid ours subtree'
all_strategies="$all_strategies recursive-ours recursive-theirs"
+not_strategies='base file index tree'
default_twohead_strategies='recursive'
default_octopus_strategies='octopus'
no_fast_forward_strategies='subtree ours'
no_trivial_strategies='recursive recur subtree ours recursive-ours recursive-theirs'
use_strategies=
+xopt=
allow_fast_forward=t
+fast_forward_only=
allow_trivial_merge=t
-squash= no_commit= log_arg=
+squash= no_commit= log_arg= rr_arg=
dropsave() {
rm -f -- "$GIT_DIR/MERGE_HEAD" "$GIT_DIR/MERGE_MSG" \
- "$GIT_DIR/MERGE_STASH" || exit 1
+ "$GIT_DIR/MERGE_STASH" "$GIT_DIR/MERGE_MODE" || exit 1
}
savestate() {
@@ -131,21 +140,34 @@ finish () {
merge_name () {
remote="$1"
rh=$(git rev-parse --verify "$remote^0" 2>/dev/null) || return
- bh=$(git show-ref -s --verify "refs/heads/$remote" 2>/dev/null)
- if test "$rh" = "$bh"
- then
- echo "$rh branch '$remote' of ."
- elif truname=$(expr "$remote" : '\(.*\)~[1-9][0-9]*$') &&
+ if truname=$(expr "$remote" : '\(.*\)~[0-9]*$') &&
git show-ref -q --verify "refs/heads/$truname" 2>/dev/null
then
echo "$rh branch '$truname' (early part) of ."
- elif test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
+ return
+ fi
+ if found_ref=$(git rev-parse --symbolic-full-name --verify \
+ "$remote" 2>/dev/null)
+ then
+ expanded=$(git check-ref-format --branch "$remote") ||
+ exit
+ if test "${found_ref#refs/heads/}" != "$found_ref"
+ then
+ echo "$rh branch '$expanded' of ."
+ return
+ elif test "${found_ref#refs/remotes/}" != "$found_ref"
+ then
+ echo "$rh remote branch '$expanded' of ."
+ return
+ fi
+ fi
+ if test "$remote" = "FETCH_HEAD" -a -r "$GIT_DIR/FETCH_HEAD"
then
sed -e 's/ not-for-merge / /' -e 1q \
"$GIT_DIR/FETCH_HEAD"
- else
- echo "$rh commit '$remote'"
+ return
fi
+ echo "$rh commit '$remote'"
}
parse_config () {
@@ -172,16 +194,36 @@ parse_config () {
--no-ff)
test "$squash" != t ||
die "You cannot combine --squash with --no-ff."
+ test "$fast_forward_only" != t ||
+ die "You cannot combine --ff-only with --no-ff."
allow_fast_forward=f ;;
+ --ff-only)
+ test "$allow_fast_forward" != f ||
+ die "You cannot combine --ff-only with --no-ff."
+ fast_forward_only=t ;;
+ --rerere-autoupdate|--no-rerere-autoupdate)
+ rr_arg=$1 ;;
-s|--strategy)
shift
case " $all_strategies " in
*" $1 "*)
- use_strategies="$use_strategies$1 " ;;
+ use_strategies="$use_strategies$1 "
+ ;;
*)
- die "available strategies are: $all_strategies" ;;
+ case " $not_strategies " in
+ *" $1 "*)
+ false
+ esac &&
+ type "git-merge-$1" >/dev/null 2>&1 ||
+ die "available strategies are: $all_strategies"
+ use_strategies="$use_strategies$1 "
+ ;;
esac
;;
+ -X)
+ shift
+ xopt="${xopt:+$xopt }$(git rev-parse --sq-quote "--$1")"
+ ;;
-m|--message)
shift
merge_msg="$1"
@@ -245,6 +287,10 @@ then
exit 1
fi
+ test "$squash" != t ||
+ die "Squash commit into empty head not supported yet"
+ test "$allow_fast_forward" = t ||
+ die "Non-fast-forward into an empty head does not make sense"
rh=$(git rev-parse --verify "$1^0") ||
die "$1 - not something we can merge"
@@ -261,12 +307,18 @@ else
# the given message. If remote is invalid we will die
# later in the common codepath so we discard the error
# in this loop.
- merge_name=$(for remote
+ merge_msg="$(
+ for remote
do
merge_name "$remote"
- done | git fmt-merge-msg $log_arg
- )
- merge_msg="${merge_msg:+$merge_msg$LF$LF}$merge_name"
+ done |
+ if test "$have_message" = t
+ then
+ git fmt-merge-msg -m "$merge_msg" $log_arg
+ else
+ git fmt-merge-msg $log_arg
+ fi
+ )"
fi
head=$(git rev-parse --verify "$head_arg"^0) || usage
@@ -335,7 +387,7 @@ case "$#" in
common=$(git merge-base --all $head "$@")
;;
*)
- common=$(git show-branch --merge-base $head "$@")
+ common=$(git merge-base --all --octopus $head "$@")
;;
esac
echo "$head" >"$GIT_DIR/ORIG_HEAD"
@@ -373,8 +425,8 @@ t,1,"$head",*)
# We are not doing octopus, not fast-forward, and have only
# one common.
git update-index --refresh 2>/dev/null
- case "$allow_trivial_merge" in
- t)
+ case "$allow_trivial_merge,$fast_forward_only" in
+ t,)
# See if it is really trivial.
git var GIT_COMMITTER_IDENT >/dev/null || exit
echo "Trying really trivial in-index merge..."
@@ -413,6 +465,11 @@ t,1,"$head",*)
;;
esac
+if test "$fast_forward_only" = t
+then
+ die "Not possible to fast-forward, aborting."
+fi
+
# We are going to make a new commit.
git var GIT_COMMITTER_IDENT >/dev/null || exit
@@ -451,7 +508,7 @@ do
# Remember which strategy left the state in the working tree
wt_strategy=$strategy
- git-merge-$strategy $common -- "$head_arg" "$@"
+ eval 'git-merge-$strategy '"$xopt"' $common -- "$head_arg" "$@"'
exit=$?
if test "$no_commit" = t && test "$exit" = 0
then
@@ -489,9 +546,9 @@ if test '' != "$result_tree"
then
if test "$allow_fast_forward" = "t"
then
- parents=$(git show-branch --independent "$head" "$@")
+ parents=$(git merge-base --independent "$head" "$@")
else
- parents=$(git rev-parse "$head" "$@")
+ parents=$(git rev-parse "$head" "$@")
fi
parents=$(echo "$parents" | sed -e 's/^/-p /')
result_commit=$(printf '%s\n' "$merge_msg" | git commit-tree $result_tree $parents) || exit
@@ -533,7 +590,15 @@ else
do
echo $remote
done >"$GIT_DIR/MERGE_HEAD"
- printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG"
+ printf '%s\n' "$merge_msg" >"$GIT_DIR/MERGE_MSG" ||
+ die "Could not write to $GIT_DIR/MERGE_MSG"
+ if test "$allow_fast_forward" != t
+ then
+ printf "%s" no-ff
+ else
+ :
+ fi >"$GIT_DIR/MERGE_MODE" ||
+ die "Could not write to $GIT_DIR/MERGE_MODE"
fi
if test "$merge_was_ok" = t
@@ -550,6 +615,6 @@ Conflicts:
sed -e 's/^[^ ]* / /' |
uniq
} >>"$GIT_DIR/MERGE_MSG"
- git rerere
+ git rerere $rr_arg
die "Automatic merge failed; fix conflicts and then commit the result."
fi
diff --git a/contrib/examples/git-revert.sh b/contrib/examples/git-revert.sh
index 49f00321b2..6bf155cbdb 100755
--- a/contrib/examples/git-revert.sh
+++ b/contrib/examples/git-revert.sh
@@ -26,6 +26,7 @@ require_work_tree
cd_to_toplevel
no_commit=
+xopt=
while case "$#" in 0) break ;; esac
do
case "$1" in
@@ -44,6 +45,16 @@ do
-x|--i-really-want-to-expose-my-private-commit-object-name)
replay=
;;
+ -X?*)
+ xopt="$xopt$(git rev-parse --sq-quote "--${1#-X}")"
+ ;;
+ --strategy-option=*)
+ xopt="$xopt$(git rev-parse --sq-quote "--${1#--strategy-option=}")"
+ ;;
+ -X|--strategy-option)
+ shift
+ xopt="$xopt$(git rev-parse --sq-quote "--$1")"
+ ;;
-*)
usage
;;
@@ -159,7 +170,7 @@ export GITHEAD_$head GITHEAD_$next
# and $prev on top of us (when reverting), or the change between
# $prev and $commit on top of us (when cherry-picking or replaying).
-git-merge-recursive $base -- $head $next &&
+eval "git merge-recursive $xopt $base -- $head $next" &&
result=$(git-write-tree 2>/dev/null) || {
mv -f .msg "$GIT_DIR/MERGE_MSG"
{
@@ -181,7 +192,6 @@ Conflicts:
esac
exit 1
}
-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
diff --git a/contrib/examples/git-svnimport.perl b/contrib/examples/git-svnimport.perl
index 4576c4a862..b09ff8f12f 100755
--- a/contrib/examples/git-svnimport.perl
+++ b/contrib/examples/git-svnimport.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
# This tool is copyright (c) 2005, Matthias Urlichs.
# It is released under the Gnu Public License, version 2.
@@ -289,7 +289,7 @@ my $current_rev = $opt_s || 1;
unless(-d $git_dir) {
system("git init");
die "Cannot init the GIT db at $git_tree: $?\n" if $?;
- system("git read-tree");
+ system("git read-tree --empty");
die "Cannot init an empty tree: $?\n" if $?;
$last_branch = $opt_o;
diff --git a/contrib/fast-import/git-p4 b/contrib/fast-import/git-p4
index c1ea643ace..98d2aee67f 100755
--- a/contrib/fast-import/git-p4
+++ b/contrib/fast-import/git-p4
@@ -222,10 +222,10 @@ def p4CmdList(cmd, stdin=None, stdin_mode='w+b', cb=None):
try:
while True:
entry = marshal.load(p4.stdout)
- if cb is not None:
- cb(entry)
- else:
- result.append(entry)
+ if cb is not None:
+ cb(entry)
+ else:
+ result.append(entry)
except EOFError:
pass
exitCode = p4.wait()
@@ -333,9 +333,13 @@ def gitBranchExists(branch):
return proc.wait() == 0;
_gitConfig = {}
-def gitConfig(key):
+def gitConfig(key, args = None): # set args to "--bool", for instance
if not _gitConfig.has_key(key):
- _gitConfig[key] = read_pipe("git config %s" % key, ignore_error=True).strip()
+ argsFilter = ""
+ if args != None:
+ argsFilter = "%s " % args
+ cmd = "git config %s%s" % (argsFilter, key)
+ _gitConfig[key] = read_pipe(cmd, ignore_error=True).strip()
return _gitConfig[key]
def p4BranchesInGit(branchesAreInRemotes = True):
@@ -445,18 +449,72 @@ def p4ChangesForPaths(depotPaths, changeRange):
changes = {}
for line in output:
- changeNum = int(line.split(" ")[1])
- changes[changeNum] = True
+ changeNum = int(line.split(" ")[1])
+ changes[changeNum] = True
changelist = changes.keys()
changelist.sort()
return changelist
+def p4PathStartsWith(path, prefix):
+ # This method tries to remedy a potential mixed-case issue:
+ #
+ # If UserA adds //depot/DirA/file1
+ # and UserB adds //depot/dira/file2
+ #
+ # we may or may not have a problem. If you have core.ignorecase=true,
+ # we treat DirA and dira as the same directory
+ ignorecase = gitConfig("core.ignorecase", "--bool") == "true"
+ if ignorecase:
+ return path.lower().startswith(prefix.lower())
+ return path.startswith(prefix)
+
class Command:
def __init__(self):
self.usage = "usage: %prog [options]"
self.needsGit = True
+class P4UserMap:
+ def __init__(self):
+ self.userMapFromPerforceServer = False
+
+ def getUserCacheFilename(self):
+ home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
+ return home + "/.gitp4-usercache.txt"
+
+ def getUserMapFromPerforceServer(self):
+ if self.userMapFromPerforceServer:
+ return
+ self.users = {}
+ self.emails = {}
+
+ for output in p4CmdList("users"):
+ if not output.has_key("User"):
+ continue
+ self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
+ self.emails[output["Email"]] = output["User"]
+
+
+ s = ''
+ for (key, val) in self.users.items():
+ s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
+
+ open(self.getUserCacheFilename(), "wb").write(s)
+ self.userMapFromPerforceServer = True
+
+ def loadUserMapFromCache(self):
+ self.users = {}
+ self.userMapFromPerforceServer = False
+ try:
+ cache = open(self.getUserCacheFilename(), "rb")
+ lines = cache.readlines()
+ cache.close()
+ for line in lines:
+ entry = line.strip().split("\t")
+ self.users[entry[0]] = entry[1]
+ except IOError:
+ self.getUserMapFromPerforceServer()
+
class P4Debug(Command):
def __init__(self):
Command.__init__(self)
@@ -537,21 +595,26 @@ class P4RollBack(Command):
return True
-class P4Submit(Command):
+class P4Submit(Command, P4UserMap):
def __init__(self):
Command.__init__(self)
+ P4UserMap.__init__(self)
self.options = [
optparse.make_option("--verbose", dest="verbose", action="store_true"),
optparse.make_option("--origin", dest="origin"),
- optparse.make_option("-M", dest="detectRename", action="store_true"),
+ optparse.make_option("-M", dest="detectRenames", action="store_true"),
+ # preserve the user, requires relevant p4 permissions
+ optparse.make_option("--preserve-user", dest="preserveUser", action="store_true"),
]
self.description = "Submit changes from git to the perforce depot."
self.usage += " [name of git branch to submit into perforce depot]"
self.interactive = True
self.origin = ""
- self.detectRename = False
+ self.detectRenames = False
self.verbose = False
+ self.preserveUser = gitConfig("git-p4.preserveUser").lower() == "true"
self.isWindows = (platform.system() == "Windows")
+ self.myP4UserId = None
def check(self):
if len(p4CmdList("opened ...")) > 0:
@@ -570,7 +633,7 @@ class P4Submit(Command):
continue
if inDescriptionSection:
- if line.startswith("Files:"):
+ if line.startswith("Files:") or line.startswith("Jobs:"):
inDescriptionSection = False
else:
continue
@@ -585,6 +648,99 @@ class P4Submit(Command):
return result
+ def p4UserForCommit(self,id):
+ # Return the tuple (perforce user,git email) for a given git commit id
+ self.getUserMapFromPerforceServer()
+ gitEmail = read_pipe("git log --max-count=1 --format='%%ae' %s" % id)
+ gitEmail = gitEmail.strip()
+ if not self.emails.has_key(gitEmail):
+ return (None,gitEmail)
+ else:
+ return (self.emails[gitEmail],gitEmail)
+
+ def checkValidP4Users(self,commits):
+ # check if any git authors cannot be mapped to p4 users
+ for id in commits:
+ (user,email) = self.p4UserForCommit(id)
+ if not user:
+ msg = "Cannot find p4 user for email %s in commit %s." % (email, id)
+ if gitConfig('git-p4.allowMissingP4Users').lower() == "true":
+ print "%s" % msg
+ else:
+ die("Error: %s\nSet git-p4.allowMissingP4Users to true to allow this." % msg)
+
+ def lastP4Changelist(self):
+ # Get back the last changelist number submitted in this client spec. This
+ # then gets used to patch up the username in the change. If the same
+ # client spec is being used by multiple processes then this might go
+ # wrong.
+ results = p4CmdList("client -o") # find the current client
+ client = None
+ for r in results:
+ if r.has_key('Client'):
+ client = r['Client']
+ break
+ if not client:
+ die("could not get client spec")
+ results = p4CmdList("changes -c %s -m 1" % client)
+ for r in results:
+ if r.has_key('change'):
+ return r['change']
+ die("Could not get changelist number for last submit - cannot patch up user details")
+
+ def modifyChangelistUser(self, changelist, newUser):
+ # fixup the user field of a changelist after it has been submitted.
+ changes = p4CmdList("change -o %s" % changelist)
+ if len(changes) != 1:
+ die("Bad output from p4 change modifying %s to user %s" %
+ (changelist, newUser))
+
+ c = changes[0]
+ if c['User'] == newUser: return # nothing to do
+ c['User'] = newUser
+ input = marshal.dumps(c)
+
+ result = p4CmdList("change -f -i", stdin=input)
+ for r in result:
+ if r.has_key('code'):
+ if r['code'] == 'error':
+ die("Could not modify user field of changelist %s to %s:%s" % (changelist, newUser, r['data']))
+ if r.has_key('data'):
+ print("Updated user field for changelist %s to %s" % (changelist, newUser))
+ return
+ die("Could not modify user field of changelist %s to %s" % (changelist, newUser))
+
+ def canChangeChangelists(self):
+ # check to see if we have p4 admin or super-user permissions, either of
+ # which are required to modify changelists.
+ results = p4CmdList("protects %s" % self.depotPath)
+ for r in results:
+ if r.has_key('perm'):
+ if r['perm'] == 'admin':
+ return 1
+ if r['perm'] == 'super':
+ return 1
+ return 0
+
+ def p4UserId(self):
+ if self.myP4UserId:
+ return self.myP4UserId
+
+ results = p4CmdList("user -o")
+ for r in results:
+ if r.has_key('User'):
+ self.myP4UserId = r['User']
+ return r['User']
+ die("Could not find your p4 user id")
+
+ def p4UserIsMe(self, p4User):
+ # return True if the given p4 user is actually me
+ me = self.p4UserId()
+ if not p4User or p4User != me:
+ return False
+ else:
+ return True
+
def prepareSubmitTemplate(self):
# remove lines in the Files section that show changes to files outside the depot path we're committing into
template = ""
@@ -599,7 +755,7 @@ class P4Submit(Command):
lastTab = path.rfind("\t")
if lastTab != -1:
path = path[:lastTab]
- if not path.startswith(self.depotPath):
+ if not p4PathStartsWith(path, self.depotPath):
continue
else:
inFilesSection = False
@@ -613,7 +769,24 @@ class P4Submit(Command):
def applyCommit(self, id):
print "Applying %s" % (read_pipe("git log --max-count=1 --pretty=oneline %s" % id))
- diffOpts = ("", "-M")[self.detectRename]
+
+ (p4User, gitEmail) = self.p4UserForCommit(id)
+
+ if not self.detectRenames:
+ # If not explicitly set check the config variable
+ self.detectRenames = gitConfig("git-p4.detectRenames").lower() == "true"
+
+ if self.detectRenames:
+ diffOpts = "-M"
+ else:
+ diffOpts = ""
+
+ if gitConfig("git-p4.detectCopies").lower() == "true":
+ diffOpts += " -C"
+
+ if gitConfig("git-p4.detectCopiesHarder").lower() == "true":
+ diffOpts += " --find-copies-harder"
+
diff = read_pipe_lines("git diff-tree -r %s \"%s^\" \"%s\"" % (diffOpts, id, id))
filesToAdd = set()
filesToDelete = set()
@@ -637,11 +810,23 @@ class P4Submit(Command):
filesToDelete.add(path)
if path in filesToAdd:
filesToAdd.remove(path)
+ elif modifier == "C":
+ src, dest = diff['src'], diff['dst']
+ p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_system("edit \"%s\"" % (dest))
+ if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_system("edit \"%s\"" % (dest))
+ filesToChangeExecBit[dest] = diff['dst_mode']
+ os.unlink(dest)
+ editedFiles.add(dest)
elif modifier == "R":
src, dest = diff['src'], diff['dst']
p4_system("integrate -Dt \"%s\" \"%s\"" % (src, dest))
- p4_system("edit \"%s\"" % (dest))
+ if diff['src_sha1'] != diff['dst_sha1']:
+ p4_system("edit \"%s\"" % (dest))
if isModeExecChanged(diff['src_mode'], diff['dst_mode']):
+ p4_system("edit \"%s\"" % (dest))
filesToChangeExecBit[dest] = diff['dst_mode']
os.unlink(dest)
editedFiles.add(dest)
@@ -704,9 +889,15 @@ class P4Submit(Command):
if self.interactive:
submitTemplate = self.prepareLogMessage(template, logMessage)
+
+ if self.preserveUser:
+ submitTemplate = submitTemplate + ("\n######## Actual user %s, modified after commit\n" % p4User)
+
if os.environ.has_key("P4DIFF"):
del(os.environ["P4DIFF"])
- diff = p4_read_pipe("diff -du ...")
+ diff = ""
+ for editedFile in editedFiles:
+ diff += p4_read_pipe("diff -du %r" % editedFile)
newdiff = ""
for newFile in filesToAdd:
@@ -718,6 +909,11 @@ class P4Submit(Command):
newdiff += "+" + line
f.close()
+ if self.checkAuthorship and not self.p4UserIsMe(p4User):
+ submitTemplate += "######## git author %s does not match your p4 account.\n" % gitEmail
+ submitTemplate += "######## Use git-p4 option --preserve-user to modify authorship\n"
+ submitTemplate += "######## Use git-p4 config git-p4.skipUserNameCheck hides this message.\n"
+
separatorLine = "######## everything below this line is just the diff #######\n"
[handle, fileName] = tempfile.mkstemp()
@@ -735,8 +931,13 @@ class P4Submit(Command):
editor = read_pipe("git var GIT_EDITOR").strip()
system(editor + " " + fileName)
+ if gitConfig("git-p4.skipSubmitEditCheck") == "true":
+ checkModTime = False
+ else:
+ checkModTime = True
+
response = "y"
- if os.stat(fileName).st_mtime <= mtime:
+ if checkModTime and (os.stat(fileName).st_mtime <= mtime):
response = "x"
while response != "y" and response != "n":
response = raw_input("Submit template unchanged. Submit anyway? [y]es, [n]o (skip this patch) ")
@@ -749,6 +950,14 @@ class P4Submit(Command):
if self.isWindows:
submitTemplate = submitTemplate.replace("\r\n", "\n")
p4_write_pipe("submit -i", submitTemplate)
+
+ if self.preserveUser:
+ if p4User:
+ # Get last changelist number. Cannot easily get it from
+ # the submit command output as the output is unmarshalled.
+ changelist = self.lastP4Changelist()
+ self.modifyChangelistUser(changelist, p4User)
+
else:
for f in editedFiles:
p4_system("revert \"%s\"" % f);
@@ -785,6 +994,10 @@ class P4Submit(Command):
if len(self.origin) == 0:
self.origin = upstream
+ if self.preserveUser:
+ if not self.canChangeChangelists():
+ die("Cannot preserve user names without p4 super-user or admin permissions")
+
if self.verbose:
print "Origin branch is " + self.origin
@@ -812,6 +1025,14 @@ class P4Submit(Command):
commits.append(line.strip())
commits.reverse()
+ if self.preserveUser or (gitConfig("git-p4.skipUserNameCheck") == "true"):
+ self.checkAuthorship = False
+ else:
+ self.checkAuthorship = True
+
+ if self.preserveUser:
+ self.checkValidP4Users(commits)
+
while len(commits) > 0:
commit = commits[0]
commits = commits[1:]
@@ -831,9 +1052,12 @@ class P4Submit(Command):
return True
-class P4Sync(Command):
+class P4Sync(Command, P4UserMap):
+ delete_actions = ( "delete", "move/delete", "purge" )
+
def __init__(self):
Command.__init__(self)
+ P4UserMap.__init__(self)
self.options = [
optparse.make_option("--branch", dest="branch"),
optparse.make_option("--detect-branches", dest="detectBranches", action="store_true"),
@@ -880,6 +1104,23 @@ class P4Sync(Command):
if gitConfig("git-p4.syncFromOrigin") == "false":
self.syncWithOrigin = False
+ #
+ # P4 wildcards are not allowed in filenames. P4 complains
+ # if you simply add them, but you can force it with "-f", in
+ # which case it translates them into %xx encoding internally.
+ # Search for and fix just these four characters. Do % last so
+ # that fixing it does not inadvertently create new %-escapes.
+ #
+ def wildcard_decode(self, path):
+ # Cannot have * in a filename in windows; untested as to
+ # what p4 would do in such a case.
+ if not self.isWindows:
+ path = path.replace("%2A", "*")
+ path = path.replace("%23", "#") \
+ .replace("%40", "@") \
+ .replace("%25", "%")
+ return path
+
def extractFilesFromCommit(self, commit):
self.cloneExclude = [re.sub(r"\.\.\.$", "", path)
for path in self.cloneExclude]
@@ -889,11 +1130,11 @@ class P4Sync(Command):
path = commit["depotFile%s" % fnum]
if [p for p in self.cloneExclude
- if path.startswith (p)]:
+ if p4PathStartsWith(path, p)]:
found = False
else:
found = [p for p in self.depotPaths
- if path.startswith (p)]
+ if p4PathStartsWith(path, p)]
if not found:
fnum = fnum + 1
continue
@@ -908,11 +1149,27 @@ class P4Sync(Command):
return files
def stripRepoPath(self, path, prefixes):
+ if self.useClientSpec:
+
+ # if using the client spec, we use the output directory
+ # specified in the client. For example, a view
+ # //depot/foo/branch/... //client/branch/foo/...
+ # will end up putting all foo/branch files into
+ # branch/foo/
+ for val in self.clientSpecDirs:
+ if path.startswith(val[0]):
+ # replace the depot path with the client path
+ path = path.replace(val[0], val[1][1])
+ # now strip out the client (//client/...)
+ path = re.sub("^(//[^/]+/)", '', path)
+ # the rest is all path
+ return path
+
if self.keepRepoPath:
prefixes = [re.sub("^(//[^/]+/).*", r'\1', prefixes[0])]
for p in prefixes:
- if path.startswith(p):
+ if p4PathStartsWith(path, p):
path = path[len(p):]
return path
@@ -923,7 +1180,7 @@ class P4Sync(Command):
while commit.has_key("depotFile%s" % fnum):
path = commit["depotFile%s" % fnum]
found = [p for p in self.depotPaths
- if path.startswith (p)]
+ if p4PathStartsWith(path, p)]
if not found:
fnum = fnum + 1
continue
@@ -952,12 +1209,13 @@ class P4Sync(Command):
# - helper for streamP4Files
def streamOneP4File(self, file, contents):
- if file["type"] == "apple":
- print "\nfile %s is a strange apple file that forks. Ignoring" % \
- file['depotFile']
- return
+ if file["type"] == "apple":
+ print "\nfile %s is a strange apple file that forks. Ignoring" % \
+ file['depotFile']
+ return
relPath = self.stripRepoPath(file['depotFile'], self.branchPrefixes)
+ relPath = self.wildcard_decode(relPath)
if verbose:
sys.stderr.write("%s\n" % relPath)
@@ -1003,22 +1261,22 @@ class P4Sync(Command):
# handle another chunk of streaming data
def streamP4FilesCb(self, marshalled):
- if marshalled.has_key('depotFile') and self.stream_have_file_info:
- # start of a new file - output the old one first
- self.streamOneP4File(self.stream_file, self.stream_contents)
- self.stream_file = {}
- self.stream_contents = []
- self.stream_have_file_info = False
+ if marshalled.has_key('depotFile') and self.stream_have_file_info:
+ # start of a new file - output the old one first
+ self.streamOneP4File(self.stream_file, self.stream_contents)
+ self.stream_file = {}
+ self.stream_contents = []
+ self.stream_have_file_info = False
- # pick up the new file information... for the
- # 'data' field we need to append to our array
- for k in marshalled.keys():
- if k == 'data':
- self.stream_contents.append(marshalled['data'])
- else:
- self.stream_file[k] = marshalled[k]
+ # pick up the new file information... for the
+ # 'data' field we need to append to our array
+ for k in marshalled.keys():
+ if k == 'data':
+ self.stream_contents.append(marshalled['data'])
+ else:
+ self.stream_file[k] = marshalled[k]
- self.stream_have_file_info = True
+ self.stream_have_file_info = True
# Stream directly from "p4 files" into "git fast-import"
def streamP4Files(self, files):
@@ -1030,16 +1288,16 @@ class P4Sync(Command):
includeFile = True
for val in self.clientSpecDirs:
if f['path'].startswith(val[0]):
- if val[1] <= 0:
+ if val[1][0] <= 0:
includeFile = False
break
if includeFile:
filesForCommit.append(f)
- if f['action'] not in ('delete', 'move/delete', 'purge'):
- filesToRead.append(f)
- else:
+ if f['action'] in self.delete_actions:
filesToDelete.append(f)
+ else:
+ filesToRead.append(f)
# deleted files...
for f in filesToDelete:
@@ -1050,14 +1308,14 @@ class P4Sync(Command):
self.stream_contents = []
self.stream_have_file_info = False
- # curry self argument
- def streamP4FilesCbSelf(entry):
- self.streamP4FilesCb(entry)
+ # curry self argument
+ def streamP4FilesCbSelf(entry):
+ self.streamP4FilesCb(entry)
- p4CmdList("-x - print",
- '\n'.join(['%s#%s' % (f['path'], f['rev'])
+ p4CmdList("-x - print",
+ '\n'.join(['%s#%s' % (f['path'], f['rev'])
for f in filesToRead]),
- cb=streamP4FilesCbSelf)
+ cb=streamP4FilesCbSelf)
# do the last chunk
if self.stream_file.has_key('depotFile'):
@@ -1066,7 +1324,7 @@ class P4Sync(Command):
def commit(self, details, files, branch, branchPrefixes, parent = ""):
epoch = details["time"]
author = details["user"]
- self.branchPrefixes = branchPrefixes
+ self.branchPrefixes = branchPrefixes
if self.verbose:
print "commit into %s" % branch
@@ -1075,10 +1333,10 @@ class P4Sync(Command):
# create a commit.
new_files = []
for f in files:
- if [p for p in branchPrefixes if f['path'].startswith(p)]:
+ if [p for p in branchPrefixes if p4PathStartsWith(f['path'], p)]:
new_files.append (f)
else:
- sys.stderr.write("Ignoring file outside of prefix: %s\n" % path)
+ sys.stderr.write("Ignoring file outside of prefix: %s\n" % f['path'])
self.gitStream.write("commit %s\n" % branch)
# gitStream.write("mark :%s\n" % details["change"])
@@ -1125,7 +1383,7 @@ class P4Sync(Command):
cleanedFiles = {}
for info in files:
- if info["action"] in ("delete", "purge"):
+ if info["action"] in self.delete_actions:
continue
cleanedFiles[info["depotFile"]] = info["rev"]
@@ -1154,41 +1412,6 @@ class P4Sync(Command):
print ("Tag %s does not match with change %s: file count is different."
% (labelDetails["label"], change))
- def getUserCacheFilename(self):
- home = os.environ.get("HOME", os.environ.get("USERPROFILE"))
- return home + "/.gitp4-usercache.txt"
-
- def getUserMapFromPerforceServer(self):
- if self.userMapFromPerforceServer:
- return
- self.users = {}
-
- for output in p4CmdList("users"):
- if not output.has_key("User"):
- continue
- self.users[output["User"]] = output["FullName"] + " <" + output["Email"] + ">"
-
-
- s = ''
- for (key, val) in self.users.items():
- s += "%s\t%s\n" % (key.expandtabs(1), val.expandtabs(1))
-
- open(self.getUserCacheFilename(), "wb").write(s)
- self.userMapFromPerforceServer = True
-
- def loadUserMapFromCache(self):
- self.users = {}
- self.userMapFromPerforceServer = False
- try:
- cache = open(self.getUserCacheFilename(), "rb")
- lines = cache.readlines()
- cache.close()
- for line in lines:
- entry = line.strip().split("\t")
- self.users[entry[0]] = entry[1]
- except IOError:
- self.getUserMapFromPerforceServer()
-
def getLabels(self):
self.labels = {}
@@ -1239,7 +1462,7 @@ class P4Sync(Command):
source = paths[0]
destination = paths[1]
## HACK
- if source.startswith(self.depotPaths[0]) and destination.startswith(self.depotPaths[0]):
+ if p4PathStartsWith(source, self.depotPaths[0]) and p4PathStartsWith(destination, self.depotPaths[0]):
source = source[len(self.depotPaths[0]):-4]
destination = destination[len(self.depotPaths[0]):-4]
@@ -1427,7 +1650,7 @@ class P4Sync(Command):
print "Doing initial import of %s from revision %s into %s" % (' '.join(self.depotPaths), revision, self.branch)
details = { "user" : "git perforce import user", "time" : int(time.time()) }
- details["desc"] = ("Initial import of %s from the state at revision %s"
+ details["desc"] = ("Initial import of %s from the state at revision %s\n"
% (' '.join(self.depotPaths), revision))
details["change"] = revision
newestRevision = 0
@@ -1438,9 +1661,16 @@ class P4Sync(Command):
% (p, revision)
for p in self.depotPaths])):
- if info['code'] == 'error':
+ if 'code' in info and info['code'] == 'error':
sys.stderr.write("p4 returned an error: %s\n"
% info['data'])
+ if info['data'].find("must refer to client") >= 0:
+ sys.stderr.write("This particular p4 error is misleading.\n")
+ sys.stderr.write("Perhaps the depot path was misspelled.\n");
+ sys.stderr.write("Depot path: %s\n" % " ".join(self.depotPaths))
+ sys.exit(1)
+ if 'p4ExitCode' in info:
+ sys.stderr.write("p4 exitcode: %s\n" % info['p4ExitCode'])
sys.exit(1)
@@ -1448,7 +1678,7 @@ class P4Sync(Command):
if change > newestRevision:
newestRevision = change
- if info["action"] in ("delete", "purge"):
+ if info["action"] in self.delete_actions:
# don't increase the file cnt, otherwise details["depotFile123"] will have gaps!
#fileCnt = fileCnt + 1
continue
@@ -1473,19 +1703,45 @@ class P4Sync(Command):
for entry in specList:
for k,v in entry.iteritems():
if k.startswith("View"):
+
+ # p4 has these %%1 to %%9 arguments in specs to
+ # reorder paths; which we can't handle (yet :)
+ if re.match('%%\d', v) != None:
+ print "Sorry, can't handle %%n arguments in client specs"
+ sys.exit(1)
+
if v.startswith('"'):
start = 1
else:
start = 0
index = v.find("...")
+
+ # save the "client view"; i.e the RHS of the view
+ # line that tells the client where to put the
+ # files for this view.
+ cv = v[index+3:].strip() # +3 to remove previous '...'
+
+ # if the client view doesn't end with a
+ # ... wildcard, then we're going to mess up the
+ # output directory, so fail gracefully.
+ if not cv.endswith('...'):
+ print 'Sorry, client view in "%s" needs to end with wildcard' % (k)
+ sys.exit(1)
+ cv=cv[:-3]
+
+ # now save the view; +index means included, -index
+ # means it should be filtered out.
v = v[start:index]
if v.startswith("-"):
v = v[1:]
- temp[v] = -len(v)
+ include = -len(v)
else:
- temp[v] = len(v)
+ include = len(v)
+
+ temp[v] = (include, cv)
+
self.clientSpecDirs = temp.items()
- self.clientSpecDirs.sort( lambda x, y: abs( y[1] ) - abs( x[1] ) )
+ self.clientSpecDirs.sort( lambda x, y: abs( y[1][0] ) - abs( x[1][0] ) )
def run(self, args):
self.depotPaths = []
@@ -1665,6 +1921,10 @@ class P4Sync(Command):
changes.sort()
else:
+ # catch "git-p4 sync" with no new branches, in a repo that
+ # does not have any existing git-p4 branches
+ if len(args) == 0 and not self.p4BranchesInGit:
+ die("No remote p4 branches. Perhaps you never did \"git p4 clone\" in here.");
if self.verbose:
print "Getting p4 changes for %s...%s" % (', '.join(self.depotPaths),
self.changeRange)
@@ -1745,10 +2005,13 @@ class P4Clone(P4Sync):
help="where to leave result of the clone"),
optparse.make_option("-/", dest="cloneExclude",
action="append", type="string",
- help="exclude depot path")
+ help="exclude depot path"),
+ optparse.make_option("--bare", dest="cloneBare",
+ action="store_true", default=False),
]
self.cloneDestination = None
self.needsGit = False
+ self.cloneBare = False
# This is required for the "append" cloneExclude action
def ensure_value(self, attr, value):
@@ -1788,11 +2051,16 @@ class P4Clone(P4Sync):
self.cloneDestination = self.defaultDestination(args)
print "Importing from %s into %s" % (', '.join(depotPaths), self.cloneDestination)
+
if not os.path.exists(self.cloneDestination):
os.makedirs(self.cloneDestination)
chdir(self.cloneDestination)
- system("git init")
- self.gitdir = os.getcwd() + "/.git"
+
+ init_cmd = [ "git", "init" ]
+ if self.cloneBare:
+ init_cmd.append("--bare")
+ subprocess.check_call(init_cmd)
+
if not P4Sync.run(self, depotPaths):
return False
if self.branch != "master":
@@ -1802,7 +2070,8 @@ class P4Clone(P4Sync):
masterbranch = "refs/heads/p4/master"
if gitBranchExists(masterbranch):
system("git branch master %s" % masterbranch)
- system("git checkout -f")
+ if not self.cloneBare:
+ system("git checkout -f")
else:
print "Could not detect main branch. No checkout/master branch created."
diff --git a/contrib/fast-import/git-p4.txt b/contrib/fast-import/git-p4.txt
index 49b335921a..caa4bb3e30 100644
--- a/contrib/fast-import/git-p4.txt
+++ b/contrib/fast-import/git-p4.txt
@@ -110,6 +110,12 @@ is not your current git branch you can also pass that as an argument:
You can override the reference branch with the --origin=mysourcebranch option.
+The Perforce changelists will be created with the user who ran git-p4. If you
+use --preserve-user then git-p4 will attempt to create Perforce changelists
+with the Perforce user corresponding to the git commit author. You need to
+have sufficient permissions within Perforce, and the git users need to have
+Perforce accounts. Permissions can be granted using 'p4 protect'.
+
If a submit fails you may have to "p4 resolve" and submit manually. You can
continue importing the remaining changes with
@@ -191,6 +197,41 @@ git-p4.useclientspec
git config [--global] git-p4.useclientspec false
+The P4CLIENT environment variable should be correctly set for p4 to be
+able to find the relevant client. This client spec will be used to
+both filter the files cloned by git and set the directory layout as
+specified in the client (this implies --keep-path style semantics).
+
+git-p4.skipSubmitModTimeCheck
+
+ git config [--global] git-p4.skipSubmitModTimeCheck false
+
+If true, submit will not check if the p4 change template has been modified.
+
+git-p4.preserveUser
+
+ git config [--global] git-p4.preserveUser false
+
+If true, attempt to preserve user names by modifying the p4 changelists. See
+the "--preserve-user" submit option.
+
+git-p4.allowMissingPerforceUsers
+
+ git config [--global] git-p4.allowMissingP4Users false
+
+If git-p4 is setting the perforce user for a commit (--preserve-user) then
+if there is no perforce user corresponding to the git author, git-p4 will
+stop. With allowMissingPerforceUsers set to true, git-p4 will use the
+current user (i.e. the behavior without --preserve-user) and carry on with
+the perforce commit.
+
+git-p4.skipUserNameCheck
+
+ git config [--global] git-p4.skipUserNameCheck false
+
+When submitting, git-p4 checks that the git commits are authored by the current
+p4 user, and warns if they are not. This disables the check.
+
Implementation Details...
=========================
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
index 3a5da4ab00..7f3afa5ac4 100755
--- a/contrib/fast-import/import-directories.perl
+++ b/contrib/fast-import/import-directories.perl
@@ -1,4 +1,4 @@
-#!/usr/bin/perl -w
+#!/usr/bin/perl
#
# Copyright 2008-2009 Peter Krefting <peter@softwolves.pp.se>
#
@@ -140,6 +140,7 @@ by whitespace or other characters.
# Globals
use strict;
+use warnings;
use integer;
my $crlfmode = 0;
my @revs;
diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README
new file mode 100644
index 0000000000..438463b160
--- /dev/null
+++ b/contrib/git-shell-commands/README
@@ -0,0 +1,18 @@
+Sample programs callable through git-shell. Place a directory named
+'git-shell-commands' in the home directory of a user whose shell is
+git-shell. Then anyone logging in as that user will be able to run
+executables in the 'git-shell-commands' directory.
+
+Provided commands:
+
+help: Prints out the names of available commands. When run
+interactively, git-shell will automatically run 'help' on startup,
+provided it exists.
+
+list: Displays any bare repository whose name ends with ".git" under
+user's home directory. No other git repositories are visible,
+although they might be clonable through git-shell. 'list' is designed
+to minimize the number of calls to git that must be made in finding
+available repositories; if your setup has additional repositories that
+should be user-discoverable, you may wish to modify 'list'
+accordingly.
diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help
new file mode 100755
index 0000000000..535770c6ec
--- /dev/null
+++ b/contrib/git-shell-commands/help
@@ -0,0 +1,18 @@
+#!/bin/sh
+
+if tty -s
+then
+ echo "Run 'help' for help, or 'exit' to leave. Available commands:"
+else
+ echo "Run 'help' for help. Available commands:"
+fi
+
+cd "$(dirname "$0")"
+
+for cmd in *
+do
+ case "$cmd" in
+ help) ;;
+ *) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
+ esac
+done
diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list
new file mode 100755
index 0000000000..6f89938821
--- /dev/null
+++ b/contrib/git-shell-commands/list
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+print_if_bare_repo='
+ if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true
+ then
+ printf "%s\n" "${1#./}"
+ fi
+'
+
+find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index 09c524105c..21989fc6ab 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -55,6 +55,11 @@
# "t=%s; printf 'http://.../?id=%%s' \$t; echo;echo; git show -C \$t; echo"
# Be careful if "..." contains things that will be expanded by shell "eval"
# or printf.
+# hooks.emailmaxlines
+# The maximum number of lines that should be included in the generated
+# email body. If not specified, there is no limit.
+# Lines beyond the limit are suppressed and counted, and a final
+# line is added indicating the number of suppressed lines.
#
# Notes
# -----
@@ -66,24 +71,16 @@
# ---------------------------- Functions
#
-# Top level email generation function. This decides what type of update
-# this is and calls the appropriate body-generation routine after outputting
-# the common header
+# Function to prepare for email generation. This decides what type
+# of update this is and whether an email should even be generated.
#
-# Note this function doesn't actually generate any email output, that is
-# taken care of by the functions it calls:
-# - generate_email_header
-# - generate_create_XXXX_email
-# - generate_update_XXXX_email
-# - generate_delete_XXXX_email
-# - generate_email_footer
-#
-generate_email()
+prep_for_email()
{
# --- Arguments
oldrev=$(git rev-parse $1)
newrev=$(git rev-parse $2)
refname="$3"
+ maxlines=$4
# --- Interpret
# 0000->1234 (create)
@@ -147,13 +144,13 @@ generate_email()
short_refname=${refname##refs/remotes/}
echo >&2 "*** Push-update of tracking branch, $refname"
echo >&2 "*** - no email generated."
- exit 0
+ return 1
;;
*)
# Anything else (is there anything else?)
echo >&2 "*** Unknown type of update to $refname ($rev_type)"
echo >&2 "*** - no email generated"
- exit 1
+ return 1
;;
esac
@@ -169,9 +166,32 @@ generate_email()
esac
echo >&2 "*** $config_name is not set so no email will be sent"
echo >&2 "*** for $refname update $oldrev->$newrev"
- exit 0
+ return 1
fi
+ return 0
+}
+
+#
+# Top level email generation function. This calls the appropriate
+# body-generation routine after outputting the common header.
+#
+# Note this function doesn't actually generate any email output, that is
+# taken care of by the functions it calls:
+# - generate_email_header
+# - generate_create_XXXX_email
+# - generate_update_XXXX_email
+# - generate_delete_XXXX_email
+# - generate_email_footer
+#
+# Note also that this function cannot 'exit' from the script; when this
+# function is running (in hook script mode), the send_mail() function
+# is already executing in another process, connected via a pipe, and
+# if this function exits without, whatever has been generated to that
+# point will be sent as an email... even if nothing has been generated.
+#
+generate_email()
+{
# Email parameters
# The email subject will contain the best description of the ref
# that we can build from the parameters
@@ -192,7 +212,12 @@ generate_email()
fn_name=atag
;;
esac
- generate_${change_type}_${fn_name}_email
+
+ if [ -z "$maxlines" ]; then
+ generate_${change_type}_${fn_name}_email
+ else
+ generate_${change_type}_${fn_name}_email | limit_lines $maxlines
+ fi
generate_email_footer
}
@@ -642,6 +667,24 @@ show_new_revisions()
}
+limit_lines()
+{
+ lines=0
+ skipped=0
+ while IFS="" read -r line; do
+ lines=$((lines + 1))
+ if [ $lines -gt $1 ]; then
+ skipped=$((skipped + 1))
+ else
+ printf "%s\n" "$line"
+ fi
+ done
+ if [ $skipped -ne 0 ]; then
+ echo "... $skipped lines suppressed ..."
+ fi
+}
+
+
send_mail()
{
if [ -n "$envelopesender" ]; then
@@ -666,7 +709,7 @@ if [ -z "$GIT_DIR" ]; then
exit 1
fi
-projectdesc=$(sed -ne '1p' "$GIT_DIR/description")
+projectdesc=$(sed -ne '1p' "$GIT_DIR/description" 2>/dev/null)
# Check if the description is unchanged from it's default, and shorten it to
# a more manageable length if it is
if expr "$projectdesc" : "Unnamed repository.*$" >/dev/null
@@ -679,6 +722,7 @@ announcerecipients=$(git config hooks.announcelist)
envelopesender=$(git config hooks.envelopesender)
emailprefix=$(git config hooks.emailprefix || echo '[SCM] ')
custom_showrev=$(git config hooks.showrev)
+maxlines=$(git config hooks.emailmaxlines)
# --- Main loop
# Allow dual mode: run from the command line just like the update hook, or
@@ -687,10 +731,11 @@ if [ -n "$1" -a -n "$2" -a -n "$3" ]; then
# Output to the terminal in command line mode - if someone wanted to
# resend an email; they could redirect the output to sendmail
# themselves
- PAGER= generate_email $2 $3 $1
+ prep_for_email $2 $3 $1 && PAGER= generate_email
else
while read oldrev newrev refname
do
- generate_email $oldrev $newrev $refname | send_mail
+ prep_for_email $oldrev $newrev $refname || continue
+ generate_email $maxlines | send_mail
done
fi
diff --git a/contrib/svn-fe/svn-fe.c b/contrib/svn-fe/svn-fe.c
index e9b9ba4da4..35db24f5ea 100644
--- a/contrib/svn-fe/svn-fe.c
+++ b/contrib/svn-fe/svn-fe.c
@@ -8,8 +8,10 @@
int main(int argc, char **argv)
{
- svndump_init(NULL);
+ if (svndump_init(NULL))
+ return 1;
svndump_read((argc > 1) ? argv[1] : NULL);
+ svndump_deinit();
svndump_reset();
return 0;
}
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
index de30f83a1f..cd075b96c5 100644
--- a/contrib/svn-fe/svn-fe.txt
+++ b/contrib/svn-fe/svn-fe.txt
@@ -12,12 +12,15 @@ svnadmin dump --incremental REPO | svn-fe [url] | git fast-import
DESCRIPTION
-----------
-Converts a Subversion dumpfile (version: 2) into input suitable for
+Converts a Subversion dumpfile into input suitable for
git-fast-import(1) and similar importers. REPO is a path to a
Subversion repository mirrored on the local disk. Remote Subversion
repositories can be mirrored on local disk using the `svnsync`
command.
+Note: this tool is very young. The details of its commandline
+interface may change in backward incompatible ways.
+
INPUT FORMAT
------------
Subversion's repository dump format is documented in full in
@@ -25,6 +28,9 @@ Subversion's repository dump format is documented in full in
Files in this format can be generated using the 'svnadmin dump' or
'svk admin dump' command.
+Dumps produced with 'svnadmin dump --deltas' (dumpfile format v3)
+are not supported.
+
OUTPUT FORMAT
-------------
The fast-import format is documented by the git-fast-import(1)
@@ -43,11 +49,9 @@ user <user@UUID>
as committer, where 'user' is the value of the `svn:author` property
and 'UUID' the repository's identifier.
-To support incremental imports, 'svn-fe' will put a `git-svn-id`
-line at the end of each commit log message if passed an url on the
-command line. This line has the form `git-svn-id: URL@REVNO UUID`.
-
-Empty directories and unknown properties are silently discarded.
+To support incremental imports, 'svn-fe' puts a `git-svn-id` line at
+the end of each commit log message if passed an url on the command
+line. This line has the form `git-svn-id: URL@REVNO UUID`.
The resulting repository will generally require further processing
to put each project in its own repository and to separate the history
@@ -56,9 +60,9 @@ may be useful for this purpose.
BUGS
----
-Litters the current working directory with .bin files for
-persistence. Will be fixed when the svn-fe infrastructure is aware of
-a Git working directory.
+Empty directories and unknown properties are silently discarded.
+
+The exit status does not reflect whether an error was detected.
SEE ALSO
--------
diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh
index cc518f3c89..5eb4a51643 100755
--- a/contrib/thunderbird-patch-inline/appp.sh
+++ b/contrib/thunderbird-patch-inline/appp.sh
@@ -1,8 +1,8 @@
-#!/bin/bash
+#!/bin/sh
# Copyright 2008 Lukas Sandström <luksan@gmail.com>
#
# AppendPatch - A script to be used together with ExternalEditor
-# for Mozilla Thunderbird to properly include pathes inline i e-mails.
+# for Mozilla Thunderbird to properly include patches inline in e-mails.
# ExternalEditor can be downloaded at http://globs.org/articles.php?lng=en&pg=2
diff --git a/contrib/workdir/git-new-workdir b/contrib/workdir/git-new-workdir
index 3ad2c0cea5..75e8b25817 100755
--- a/contrib/workdir/git-new-workdir
+++ b/contrib/workdir/git-new-workdir
@@ -42,7 +42,7 @@ then
fi
# don't link to a workdir
-if test -L "$git_dir/config"
+if test -h "$git_dir/config"
then
die "\"$orig_git\" is a working directory only, please specify" \
"a complete repository."