summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rwxr-xr-x[-rw-r--r--]contrib/buildsystems/engine.pl0
-rwxr-xr-x[-rw-r--r--]contrib/buildsystems/generate0
-rwxr-xr-x[-rw-r--r--]contrib/buildsystems/parse.pl0
-rw-r--r--contrib/completion/git-completion.bash59
-rw-r--r--contrib/completion/git-completion.tcsh2
-rw-r--r--contrib/completion/git-completion.zsh11
-rw-r--r--contrib/completion/git-prompt.sh21
-rwxr-xr-xcontrib/contacts/git-contacts2
-rw-r--r--contrib/credential/gnome-keyring/git-credential-gnome-keyring.c85
-rwxr-xr-xcontrib/examples/git-checkout.sh2
-rwxr-xr-xcontrib/examples/git-commit.sh2
-rwxr-xr-xcontrib/examples/git-reset.sh4
-rwxr-xr-xcontrib/examples/git-whatchanged.sh4
-rwxr-xr-xcontrib/fast-import/import-directories.perl4
-rwxr-xr-xcontrib/git-resurrect.sh1
-rw-r--r--contrib/hooks/multimail/CHANGES33
-rw-r--r--contrib/hooks/multimail/README46
-rw-r--r--contrib/hooks/multimail/README.Git4
-rwxr-xr-xcontrib/hooks/multimail/git_multimail.py218
-rwxr-xr-xcontrib/hooks/multimail/post-receive4
-rwxr-xr-xcontrib/hooks/post-receive-email1
-rwxr-xr-x[-rw-r--r--]contrib/hooks/pre-auto-gc-battery1
-rwxr-xr-x[-rw-r--r--]contrib/hooks/setgitperms.perl0
-rwxr-xr-x[-rw-r--r--]contrib/hooks/update-paranoid0
-rw-r--r--contrib/mw-to-git/Makefile9
-rw-r--r--contrib/p4import/README1
-rw-r--r--contrib/p4import/git-p4import.py365
-rw-r--r--contrib/p4import/git-p4import.txt167
-rwxr-xr-xcontrib/remote-helpers/git-remote-bzr58
-rwxr-xr-xcontrib/remote-helpers/git-remote-hg32
-rwxr-xr-xcontrib/remote-helpers/test-bzr.sh53
-rwxr-xr-xcontrib/remote-helpers/test-hg-bidi.sh3
-rwxr-xr-xcontrib/remote-helpers/test-hg-hg-git.sh3
-rwxr-xr-xcontrib/remote-helpers/test-hg.sh186
-rwxr-xr-xcontrib/subtree/git-subtree.sh23
-rw-r--r--contrib/subtree/git-subtree.txt14
-rw-r--r--contrib/svn-fe/svn-fe.txt4
37 files changed, 693 insertions, 729 deletions
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index 23da787dc5..23da787dc5 100644..100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
diff --git a/contrib/buildsystems/generate b/contrib/buildsystems/generate
index bc10f25ff2..bc10f25ff2 100644..100755
--- a/contrib/buildsystems/generate
+++ b/contrib/buildsystems/generate
diff --git a/contrib/buildsystems/parse.pl b/contrib/buildsystems/parse.pl
index c9656ece99..c9656ece99 100644..100755
--- a/contrib/buildsystems/parse.pl
+++ b/contrib/buildsystems/parse.pl
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index dba3c15700..2c59a76bc2 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -1,5 +1,3 @@
-#!bash
-#
# bash/zsh completion support for core Git.
#
# Copyright (C) 2006,2007 Shawn O. Pearce <spearce@spearce.org>
@@ -180,9 +178,9 @@ _get_comp_words_by_ref ()
}
fi
-__gitcompadd ()
+__gitcompappend ()
{
- local i=0
+ local i=${#COMPREPLY[@]}
for x in $1; do
if [[ "$x" == "$3"* ]]; then
COMPREPLY[i++]="$2$x$4"
@@ -190,6 +188,12 @@ __gitcompadd ()
done
}
+__gitcompadd ()
+{
+ COMPREPLY=()
+ __gitcompappend "$@"
+}
+
# Generates completion reply, appending a space to possible completion words,
# if necessary.
# It accepts 1 to 4 arguments:
@@ -220,6 +224,14 @@ __gitcomp ()
esac
}
+# Variation of __gitcomp_nl () that appends to the existing list of
+# completion candidates, COMPREPLY.
+__gitcomp_nl_append ()
+{
+ local IFS=$'\n'
+ __gitcompappend "$1" "${2-}" "${3-$cur}" "${4- }"
+}
+
# Generates completion reply from newline-separated possible completion words
# by appending a space to all of them.
# It accepts 1 to 4 arguments:
@@ -231,8 +243,8 @@ __gitcomp ()
# appended.
__gitcomp_nl ()
{
- local IFS=$'\n'
- __gitcompadd "$1" "${2-}" "${3-$cur}" "${4- }"
+ COMPREPLY=()
+ __gitcomp_nl_append "$@"
}
# Generates completion reply with compgen from newline-separated possible
@@ -673,7 +685,6 @@ __git_list_porcelain_commands ()
index-pack) : plumbing;;
init-db) : deprecated;;
local-fetch) : plumbing;;
- lost-found) : infrequent;;
ls-files) : plumbing;;
ls-remote) : plumbing;;
ls-tree) : plumbing;;
@@ -687,14 +698,12 @@ __git_list_porcelain_commands ()
pack-refs) : plumbing;;
parse-remote) : plumbing;;
patch-id) : plumbing;;
- peek-remote) : plumbing;;
prune) : plumbing;;
prune-packed) : plumbing;;
quiltimport) : import;;
read-tree) : plumbing;;
receive-pack) : plumbing;;
remote-*) : transport;;
- repo-config) : deprecated;;
rerere) : plumbing;;
rev-list) : plumbing;;
rev-parse) : plumbing;;
@@ -707,7 +716,6 @@ __git_list_porcelain_commands ()
ssh-*) : transport;;
stripspace) : plumbing;;
symbolic-ref) : plumbing;;
- tar-tree) : deprecated;;
unpack-file) : plumbing;;
unpack-objects) : plumbing;;
update-index) : plumbing;;
@@ -1213,14 +1221,20 @@ _git_difftool ()
__git_complete_revlist_file
}
+__git_fetch_recurse_submodules="yes on-demand no"
+
__git_fetch_options="
--quiet --verbose --append --upload-pack --force --keep --depth=
- --tags --no-tags --all --prune --dry-run
+ --tags --no-tags --all --prune --dry-run --recurse-submodules=
"
_git_fetch ()
{
case "$cur" in
+ --recurse-submodules=*)
+ __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}"
+ return
+ ;;
--*)
__gitcomp "$__git_fetch_options"
return
@@ -1491,6 +1505,12 @@ _git_mergetool ()
_git_merge_base ()
{
+ case "$cur" in
+ --*)
+ __gitcomp "--octopus --independent --is-ancestor --fork-point"
+ return
+ ;;
+ esac
__gitcomp_nl "$(__git_refs)"
}
@@ -1569,6 +1589,10 @@ _git_pull ()
__git_complete_strategy && return
case "$cur" in
+ --recurse-submodules=*)
+ __gitcomp "$__git_fetch_recurse_submodules" "" "${cur##--recurse-submodules=}"
+ return
+ ;;
--*)
__gitcomp "
--rebase --no-rebase
@@ -1581,6 +1605,8 @@ _git_pull ()
__git_complete_remote_or_refspec
}
+__git_push_recurse_submodules="check on-demand"
+
_git_push ()
{
case "$prev" in
@@ -1593,10 +1619,15 @@ _git_push ()
__gitcomp_nl "$(__git_remotes)" "" "${cur##--repo=}"
return
;;
+ --recurse-submodules=*)
+ __gitcomp "$__git_push_recurse_submodules" "" "${cur##--recurse-submodules=}"
+ return
+ ;;
--*)
__gitcomp "
--all --mirror --tags --dry-run --force --verbose
--receive-pack= --repo= --set-upstream
+ --recurse-submodules=
"
return
;;
@@ -1623,7 +1654,7 @@ _git_rebase ()
--preserve-merges --stat --no-stat
--committer-date-is-author-date --ignore-date
--ignore-whitespace --whitespace=
- --autosquash
+ --autosquash --fork-point --no-fork-point
"
return
@@ -1833,6 +1864,7 @@ _git_config ()
branch.*)
local pfx="${cur%.*}." cur_="${cur#*.}"
__gitcomp_nl "$(__git_heads)" "$pfx" "$cur_" "."
+ __gitcomp_nl_append $'autosetupmerge\nautosetuprebase\n' "$pfx" "$cur_"
return
;;
guitool.*.*)
@@ -1875,6 +1907,7 @@ _git_config ()
remote.*)
local pfx="${cur%.*}." cur_="${cur#*.}"
__gitcomp_nl "$(__git_remotes)" "$pfx" "$cur_" "."
+ __gitcomp_nl_append "pushdefault" "$pfx" "$cur_"
return
;;
url.*.*)
@@ -1997,6 +2030,7 @@ _git_config ()
fetch.unpackLimit
format.attach
format.cc
+ format.coverLetter
format.headers
format.numbered
format.pretty
@@ -2530,6 +2564,7 @@ __git_main ()
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
+ words[1]=$expansion
completion_func="_git_${expansion//-/_}"
declare -f $completion_func >/dev/null && $completion_func
fi
diff --git a/contrib/completion/git-completion.tcsh b/contrib/completion/git-completion.tcsh
index eaacaf0c3e..6104a42a23 100644
--- a/contrib/completion/git-completion.tcsh
+++ b/contrib/completion/git-completion.tcsh
@@ -1,5 +1,3 @@
-#!tcsh
-#
# tcsh completion support for core Git.
#
# Copyright (C) 2012 Marc Khouzam <marc.khouzam@gmail.com>
diff --git a/contrib/completion/git-completion.zsh b/contrib/completion/git-completion.zsh
index fac5e711eb..9f6f0fa558 100644
--- a/contrib/completion/git-completion.zsh
+++ b/contrib/completion/git-completion.zsh
@@ -30,10 +30,10 @@ if [ -z "$script" ]; then
local -a locations
local e
locations=(
+ $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
'/etc/bash_completion.d/git' # fedora, old debian
'/usr/share/bash-completion/completions/git' # arch, ubuntu, new debian
'/usr/share/bash-completion/git' # gentoo
- $(dirname ${funcsourcetrace[1]%:*})/git-completion.bash
)
for e in $locations; do
test -f $e && script="$e" && break
@@ -76,6 +76,14 @@ __gitcomp_nl ()
compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
}
+__gitcomp_nl_append ()
+{
+ emulate -L zsh
+
+ local IFS=$'\n'
+ compadd -Q -S "${4- }" -p "${2-}" -- ${=1} && _ret=0
+}
+
__gitcomp_file ()
{
emulate -L zsh
@@ -96,6 +104,7 @@ __git_zsh_bash_func ()
local expansion=$(__git_aliased_command "$command")
if [ -n "$expansion" ]; then
+ words[1]=$expansion
completion_func="_git_${expansion//-/_}"
declare -f $completion_func >/dev/null && $completion_func
fi
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 7b732d2aeb..54489080f8 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -259,6 +259,13 @@ __git_ps1_colorize_gitstring ()
r="$c_clear$r"
}
+eread ()
+{
+ f="$1"
+ shift
+ test -r "$f" && read "$@" <"$f"
+}
+
# __git_ps1 accepts 0 or 1 arguments (i.e., format string)
# when called from PS1 using command substitution
# in this mode it prints text to add to bash PS1 prompt (includes branch name)
@@ -321,9 +328,9 @@ __git_ps1 ()
local step=""
local total=""
if [ -d "$g/rebase-merge" ]; then
- read b 2>/dev/null <"$g/rebase-merge/head-name"
- read step 2>/dev/null <"$g/rebase-merge/msgnum"
- read total 2>/dev/null <"$g/rebase-merge/end"
+ eread "$g/rebase-merge/head-name" b
+ eread "$g/rebase-merge/msgnum" step
+ eread "$g/rebase-merge/end" total
if [ -f "$g/rebase-merge/interactive" ]; then
r="|REBASE-i"
else
@@ -331,10 +338,10 @@ __git_ps1 ()
fi
else
if [ -d "$g/rebase-apply" ]; then
- read step 2>/dev/null <"$g/rebase-apply/next"
- read total 2>/dev/null <"$g/rebase-apply/last"
+ eread "$g/rebase-apply/next" step
+ eread "$g/rebase-apply/last" total
if [ -f "$g/rebase-apply/rebasing" ]; then
- read b 2>/dev/null <"$g/rebase-apply/head-name"
+ eread "$g/rebase-apply/head-name" b
r="|REBASE"
elif [ -f "$g/rebase-apply/applying" ]; then
r="|AM"
@@ -358,7 +365,7 @@ __git_ps1 ()
b="$(git symbolic-ref HEAD 2>/dev/null)"
else
local head=""
- if ! read head 2>/dev/null <"$g/HEAD"; then
+ if ! eread "$g/HEAD" head; then
if [ $pcmode = yes ]; then
PS1="$ps1pc_start$ps1pc_end"
fi
diff --git a/contrib/contacts/git-contacts b/contrib/contacts/git-contacts
index 428cc1a9a1..dbe2abf277 100755
--- a/contrib/contacts/git-contacts
+++ b/contrib/contacts/git-contacts
@@ -96,8 +96,6 @@ sub scan_patches {
next unless $id;
if (m{^--- (?:a/(.+)|/dev/null)$}) {
$source = $1;
- } elsif (/^--- /) {
- die "Cannot parse hunk source: $_\n";
} elsif (/^@@ -(\d+)(?:,(\d+))?/ && $source) {
my $len = defined($2) ? $2 : 1;
push @{$sources->{$source}{$id}}, [$1, $len] if $len;
diff --git a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
index 635c96bc56..2a317fca44 100644
--- a/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
+++ b/contrib/credential/gnome-keyring/git-credential-gnome-keyring.c
@@ -60,7 +60,7 @@
#define gnome_keyring_memory_free gnome_keyring_free_password
#define gnome_keyring_memory_strdup g_strdup
-static const char* gnome_keyring_result_to_message(GnomeKeyringResult result)
+static const char *gnome_keyring_result_to_message(GnomeKeyringResult result)
{
switch (result) {
case GNOME_KEYRING_RESULT_OK:
@@ -95,9 +95,9 @@ static const char* gnome_keyring_result_to_message(GnomeKeyringResult result)
static void gnome_keyring_done_cb(GnomeKeyringResult result, gpointer user_data)
{
- gpointer *data = (gpointer*) user_data;
- int *done = (int*) data[0];
- GnomeKeyringResult *r = (GnomeKeyringResult*) data[1];
+ gpointer *data = (gpointer *)user_data;
+ int *done = (int *)data[0];
+ GnomeKeyringResult *r = (GnomeKeyringResult *)data[1];
*r = result;
*done = 1;
@@ -130,34 +130,30 @@ static GnomeKeyringResult gnome_keyring_item_delete_sync(const char *keyring, gu
/*
* This credential struct and API is simplified from git's credential.{h,c}
*/
-struct credential
-{
- char *protocol;
- char *host;
+struct credential {
+ char *protocol;
+ char *host;
unsigned short port;
- char *path;
- char *username;
- char *password;
+ char *path;
+ char *username;
+ char *password;
};
-#define CREDENTIAL_INIT \
- { NULL,NULL,0,NULL,NULL,NULL }
+#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
-typedef int (*credential_op_cb)(struct credential*);
+typedef int (*credential_op_cb)(struct credential *);
-struct credential_operation
-{
- char *name;
+struct credential_operation {
+ char *name;
credential_op_cb op;
};
-#define CREDENTIAL_OP_END \
- { NULL,NULL }
+#define CREDENTIAL_OP_END { NULL, NULL }
/* ----------------- GNOME Keyring functions ----------------- */
/* create a special keyring option string, if path is given */
-static char* keyring_object(struct credential *c)
+static char *keyring_object(struct credential *c)
{
if (!c->path)
return NULL;
@@ -170,7 +166,7 @@ static char* keyring_object(struct credential *c)
static int keyring_get(struct credential *c)
{
- char* object = NULL;
+ char *object = NULL;
GList *entries;
GnomeKeyringNetworkPasswordData *password_data;
GnomeKeyringResult result;
@@ -204,7 +200,7 @@ static int keyring_get(struct credential *c)
}
/* pick the first one from the list */
- password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
+ password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
gnome_keyring_memory_free(c->password);
c->password = gnome_keyring_memory_strdup(password_data->password);
@@ -221,7 +217,7 @@ static int keyring_get(struct credential *c)
static int keyring_store(struct credential *c)
{
guint32 item_id;
- char *object = NULL;
+ char *object = NULL;
GnomeKeyringResult result;
/*
@@ -262,7 +258,7 @@ static int keyring_store(struct credential *c)
static int keyring_erase(struct credential *c)
{
- char *object = NULL;
+ char *object = NULL;
GList *entries;
GnomeKeyringNetworkPasswordData *password_data;
GnomeKeyringResult result;
@@ -298,22 +294,20 @@ static int keyring_erase(struct credential *c)
if (result == GNOME_KEYRING_RESULT_CANCELLED)
return EXIT_SUCCESS;
- if (result != GNOME_KEYRING_RESULT_OK)
- {
+ if (result != GNOME_KEYRING_RESULT_OK) {
g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
/* pick the first one from the list (delete all matches?) */
- password_data = (GnomeKeyringNetworkPasswordData *) entries->data;
+ password_data = (GnomeKeyringNetworkPasswordData *)entries->data;
result = gnome_keyring_item_delete_sync(
password_data->keyring, password_data->item_id);
gnome_keyring_network_password_list_free(entries);
- if (result != GNOME_KEYRING_RESULT_OK)
- {
+ if (result != GNOME_KEYRING_RESULT_OK) {
g_critical("%s", gnome_keyring_result_to_message(result));
return EXIT_FAILURE;
}
@@ -325,9 +319,8 @@ static int keyring_erase(struct credential *c)
* Table with helper operation callbacks, used by generic
* credential helper main function.
*/
-static struct credential_operation const credential_helper_ops[] =
-{
- { "get", keyring_get },
+static struct credential_operation const credential_helper_ops[] = {
+ { "get", keyring_get },
{ "store", keyring_store },
{ "erase", keyring_erase },
CREDENTIAL_OP_END
@@ -353,24 +346,23 @@ static void credential_clear(struct credential *c)
static int credential_read(struct credential *c)
{
- char *buf;
+ char *buf;
size_t line_len;
- char *key;
- char *value;
+ char *key;
+ char *value;
key = buf = gnome_keyring_memory_alloc(1024);
- while (fgets(buf, 1024, stdin))
- {
+ while (fgets(buf, 1024, stdin)) {
line_len = strlen(buf);
if (line_len && buf[line_len-1] == '\n')
- buf[--line_len]='\0';
+ buf[--line_len] = '\0';
if (!line_len)
break;
- value = strchr(buf,'=');
+ value = strchr(buf, '=');
if (!value) {
g_warning("invalid credential line: %s", key);
gnome_keyring_memory_free(buf);
@@ -384,7 +376,7 @@ static int credential_read(struct credential *c)
} else if (!strcmp(key, "host")) {
g_free(c->host);
c->host = g_strdup(value);
- value = strrchr(c->host,':');
+ value = strrchr(c->host, ':');
if (value) {
*value++ = '\0';
c->port = atoi(value);
@@ -398,7 +390,8 @@ static int credential_read(struct credential *c)
} else if (!strcmp(key, "password")) {
gnome_keyring_memory_free(c->password);
c->password = gnome_keyring_memory_strdup(value);
- while (*value) *value++ = '\0';
+ while (*value)
+ *value++ = '\0';
}
/*
* Ignore other lines; we don't know what they mean, but
@@ -429,16 +422,16 @@ static void credential_write(const struct credential *c)
static void usage(const char *name)
{
struct credential_operation const *try_op = credential_helper_ops;
- const char *basename = strrchr(name,'/');
+ const char *basename = strrchr(name, '/');
basename = (basename) ? basename + 1 : name;
fprintf(stderr, "usage: %s <", basename);
while (try_op->name) {
- fprintf(stderr,"%s",(try_op++)->name);
+ fprintf(stderr, "%s", (try_op++)->name);
if (try_op->name)
- fprintf(stderr,"%s","|");
+ fprintf(stderr, "%s", "|");
}
- fprintf(stderr,"%s",">\n");
+ fprintf(stderr, "%s", ">\n");
}
int main(int argc, char *argv[])
@@ -446,7 +439,7 @@ int main(int argc, char *argv[])
int ret = EXIT_SUCCESS;
struct credential_operation const *try_op = credential_helper_ops;
- struct credential cred = CREDENTIAL_INIT;
+ struct credential cred = CREDENTIAL_INIT;
if (!argv[1]) {
usage(argv[0]);
diff --git a/contrib/examples/git-checkout.sh b/contrib/examples/git-checkout.sh
index 1a7689a48f..d2c1f98b86 100755
--- a/contrib/examples/git-checkout.sh
+++ b/contrib/examples/git-checkout.sh
@@ -168,7 +168,7 @@ cd_to_toplevel
# branch. However, if "git checkout HEAD" detaches the HEAD
# from the current branch, even though that may be logically
# correct, it feels somewhat funny. More importantly, we do not
-# want "git checkout" nor "git checkout -f" to detach HEAD.
+# want "git checkout" or "git checkout -f" to detach HEAD.
detached=
detach_warn=
diff --git a/contrib/examples/git-commit.sh b/contrib/examples/git-commit.sh
index 23ffb028d1..4aab1a6d21 100755
--- a/contrib/examples/git-commit.sh
+++ b/contrib/examples/git-commit.sh
@@ -280,7 +280,7 @@ case "$#,$also,$only,$amend" in
0,,,*)
;;
*,,,*)
- only_include_assumed="# Explicit paths specified without -i nor -o; assuming --only paths..."
+ only_include_assumed="# Explicit paths specified without -i or -o; assuming --only paths..."
also=
;;
esac
diff --git a/contrib/examples/git-reset.sh b/contrib/examples/git-reset.sh
index bafeb52cd1..cb1bbf3b90 100755
--- a/contrib/examples/git-reset.sh
+++ b/contrib/examples/git-reset.sh
@@ -40,7 +40,7 @@ case "$1" in --) shift ;; esac
# git reset --mixed tree [--] paths... can be used to
# load chosen paths from the tree into the index without
-# affecting the working tree nor HEAD.
+# affecting the working tree or HEAD.
if test $# != 0
then
test "$reset_type" = "--mixed" ||
@@ -60,7 +60,7 @@ then
update=-u
fi
-# Soft reset does not touch the index file nor the working tree
+# Soft reset does not touch the index file or the working tree
# at all, but requires them in a good order. Other resets reset
# the index file to the tree object we are switching to.
if test "$reset_type" = "--soft"
diff --git a/contrib/examples/git-whatchanged.sh b/contrib/examples/git-whatchanged.sh
index 1fb9feb348..2edbdc6d99 100755
--- a/contrib/examples/git-whatchanged.sh
+++ b/contrib/examples/git-whatchanged.sh
@@ -9,12 +9,12 @@ case "$0" in
*whatchanged)
count=
test -z "$diff_tree_flags" &&
- diff_tree_flags=$(git-repo-config --get whatchanged.difftree)
+ diff_tree_flags=$(git config --get whatchanged.difftree)
diff_tree_default_flags='-c -M --abbrev' ;;
*show)
count=-n1
test -z "$diff_tree_flags" &&
- diff_tree_flags=$(git-repo-config --get show.difftree)
+ diff_tree_flags=$(git config --get show.difftree)
diff_tree_default_flags='--cc --always' ;;
esac
test -z "$diff_tree_flags" &&
diff --git a/contrib/fast-import/import-directories.perl b/contrib/fast-import/import-directories.perl
index 7f3afa5ac4..4dec1f18e4 100755
--- a/contrib/fast-import/import-directories.perl
+++ b/contrib/fast-import/import-directories.perl
@@ -109,8 +109,8 @@ was available previously is not included in this revision, it will
be removed.
If an on-disk revision is incomplete, you can point to files from
-a previous revision. There are no restriction as to where the source
-files are located, nor to the names of them.
+a previous revision. There are no restrictions on where the source
+files are located, nor on their names.
[3.files]
; the key is the path inside the repository, the value is the path
diff --git a/contrib/git-resurrect.sh b/contrib/git-resurrect.sh
index a4ed4c3c62..d7e97bbc76 100755
--- a/contrib/git-resurrect.sh
+++ b/contrib/git-resurrect.sh
@@ -10,6 +10,7 @@ is rather slow but allows you to resurrect other people's topic
branches."
OPTIONS_KEEPDASHDASH=
+OPTIONS_STUCKLONG=
OPTIONS_SPEC="\
git resurrect $USAGE
--
diff --git a/contrib/hooks/multimail/CHANGES b/contrib/hooks/multimail/CHANGES
new file mode 100644
index 0000000000..3603d56c26
--- /dev/null
+++ b/contrib/hooks/multimail/CHANGES
@@ -0,0 +1,33 @@
+Release 1.0.0
+=============
+
+* Fix encoding of non-ASCII email addresses in email headers.
+
+* Fix backwards-compatibility bugs for older Python 2.x versions.
+
+* Fix a backwards-compatibility bug for Git 1.7.1.
+
+* Add an option commitDiffOpts to customize logs for revisions.
+
+* Pass "-oi" to sendmail by default to prevent premature termination
+ on a line containing only ".".
+
+* Stagger email "Date:" values in an attempt to help mail clients
+ thread the emails in the right order.
+
+* If a mailing list setting is missing, just skip sending the
+ corresponding email (with a warning) instead of failing.
+
+* Add a X-Git-Host header that can be used for email filtering.
+
+* Allow the sender's fully-qualified domain name to be configured.
+
+* Minor documentation improvements.
+
+* Add this CHANGES file.
+
+
+Release 0.9.0
+=============
+
+* Initial release.
diff --git a/contrib/hooks/multimail/README b/contrib/hooks/multimail/README
index 9904396710..477d65fed3 100644
--- a/contrib/hooks/multimail/README
+++ b/contrib/hooks/multimail/README
@@ -91,9 +91,10 @@ Requirements
been tested; if you do so, please report your results.)
* To send emails using the default configuration, a standard sendmail
- program must be located at '/usr/sbin/sendmail' and configured
- correctly to send emails. If this is not the case, see the
- multimailhook.mailer configuration variable below for how to
+ program must be located at '/usr/sbin/sendmail' or
+ '/usr/lib/sendmail' and must be configured correctly to send emails.
+ If this is not the case, set multimailhook.sendmailCommand, or see
+ the multimailhook.mailer configuration variable below for how to
configure git-multimail to send emails via an SMTP server.
@@ -169,7 +170,7 @@ multimailhook.repoName
for gitolite repositories, or otherwise to derive this value from
the repository path name.
-multimailhook.mailinglist
+multimailhook.mailingList
The list of email addresses to which notification emails should be
sent, as RFC 2822 email addresses separated by commas. This
@@ -184,26 +185,29 @@ multimailhook.refchangeList
reference changes should be sent, as RFC 2822 email addresses
separated by commas. This configuration option can be
multivalued. The default is the value in
- multimailhook.mailinglist. Set this value to the empty string to
- prevent reference change emails from being sent.
+ multimailhook.mailingList. Set this value to the empty string to
+ prevent reference change emails from being sent even if
+ multimailhook.mailingList is set.
multimailhook.announceList
The list of email addresses to which emails about new annotated
tags should be sent, as RFC 2822 email addresses separated by
commas. This configuration option can be multivalued. The
- default is the value in multimailhook.refchangelist or
- multimailhook.mailinglist. Set this value to the empty string to
- prevent annotated tag announcement emails from being sent.
+ default is the value in multimailhook.refchangeList or
+ multimailhook.mailingList. Set this value to the empty string to
+ prevent annotated tag announcement emails from being sent even if
+ one of the other values is set.
multimailhook.commitList
The list of email addresses to which emails about individual new
commits should be sent, as RFC 2822 email addresses separated by
commas. This configuration option can be multivalued. The
- default is the value in multimailhook.mailinglist. Set this value
+ default is the value in multimailhook.mailingList. Set this value
to the empty string to prevent notification emails about
- individual commits from being sent.
+ individual commits from being sent even if
+ multimailhook.mailingList is set.
multimailhook.announceShortlog
@@ -237,10 +241,11 @@ multimailhook.mailer
quoting is allowed in the value of this setting, but remember that
Git requires double-quotes to be escaped; e.g.,
- git config multimailhook.sendmailcommand '/usr/sbin/sendmail -t -F \"Git Repo\"'
+ git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
- Default is '/usr/sbin/sendmail -t' or '/usr/lib/sendmail
- -t' (depending on which file is present and executable).
+ Default is '/usr/sbin/sendmail -oi -t' or
+ '/usr/lib/sendmail -oi -t' (depending on which file is
+ present and executable).
multimailhook.envelopeSender
@@ -344,6 +349,14 @@ multimailhook.logOpts
[multimailhook]
logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
+multimailhook.commitLogOpts
+
+ Options passed to "git log" to generate additional info for
+ revision change emails. For example, adding --ignore-all-spaces
+ will suppress whitespace changes. The default options are "-C
+ --stat -p --cc". Shell quoting is allowed; see
+ multimailhook.logOpts for details.
+
multimailhook.emailDomain
Domain name appended to the username of the person doing the push
@@ -381,8 +394,8 @@ Email filtering aids
All emails include extra headers to enable fine tuned filtering and
give information for debugging. All emails include the headers
-"X-Git-Repo", "X-Git-Refname", and "X-Git-Reftype". ReferenceChange
-emails also include headers "X-Git-Oldrev" and "X-Git-Newrev";
+"X-Git-Host", "X-Git-Repo", "X-Git-Refname", and "X-Git-Reftype".
+ReferenceChange emails also include headers "X-Git-Oldrev" and "X-Git-Newrev";
Revision emails also include header "X-Git-Rev".
@@ -463,6 +476,7 @@ The git-multimail project itself is currently hosted on GitHub:
We use the GitHub issue tracker to keep track of bugs and feature
requests, and GitHub pull requests to exchange patches (though, if you
prefer, you can send patches via the Git mailing list with cc to me).
+Please sign off your patches as per the Git project practice.
Please note that although a copy of git-multimail will probably be
distributed in the "contrib" section of the main Git project,
diff --git a/contrib/hooks/multimail/README.Git b/contrib/hooks/multimail/README.Git
index 9c2e66a69a..129b771410 100644
--- a/contrib/hooks/multimail/README.Git
+++ b/contrib/hooks/multimail/README.Git
@@ -6,10 +6,10 @@ website:
https://github.com/mhagger/git-multimail
The version in this directory was obtained from the upstream project
-on 2013-07-14 and consists of the "git-multimail" subdirectory from
+on 2014-04-07 and consists of the "git-multimail" subdirectory from
revision
- 1a5cb09c698a74d15a715a86b09ead5f56bf4b06
+ 1b32653bafc4f902535b9fc1cd9cae911325b870
Please see the README file in this directory for information about how
to report bugs or contribute to git-multimail.
diff --git a/contrib/hooks/multimail/git_multimail.py b/contrib/hooks/multimail/git_multimail.py
index 81c6a51706..8b58ed6444 100755
--- a/contrib/hooks/multimail/git_multimail.py
+++ b/contrib/hooks/multimail/git_multimail.py
@@ -1,6 +1,6 @@
#! /usr/bin/env python2
-# Copyright (c) 2012,2013 Michael Haggerty
+# Copyright (c) 2012-2014 Michael Haggerty and others
# Derived from contrib/hooks/post-receive-email, which is
# Copyright (c) 2007 Andy Parkins
# and also includes contributions by other authors.
@@ -49,21 +49,25 @@ import sys
import os
import re
import bisect
+import socket
import subprocess
import shlex
import optparse
import smtplib
+import time
try:
from email.utils import make_msgid
from email.utils import getaddresses
from email.utils import formataddr
+ from email.utils import formatdate
from email.header import Header
except ImportError:
# Prior to Python 2.5, the email module used different names:
from email.Utils import make_msgid
from email.Utils import getaddresses
from email.Utils import formataddr
+ from email.Utils import formatdate
from email.Header import Header
@@ -73,6 +77,7 @@ ZEROS = '0' * 40
LOGBEGIN = '- Log -----------------------------------------------------------------\n'
LOGEND = '-----------------------------------------------------------------------\n'
+ADDR_HEADERS = set(['from', 'to', 'cc', 'bcc', 'reply-to', 'sender'])
# It is assumed in many places that the encoding is uniformly UTF-8,
# so changing these constants is unsupported. But define them here
@@ -95,6 +100,7 @@ REF_DELETED_SUBJECT_TEMPLATE = (
)
REFCHANGE_HEADER_TEMPLATE = """\
+Date: %(send_date)s
To: %(recipients)s
Subject: %(subject)s
MIME-Version: 1.0
@@ -103,6 +109,7 @@ Content-Transfer-Encoding: 8bit
Message-ID: %(msgid)s
From: %(fromaddr)s
Reply-To: %(reply_to)s
+X-Git-Host: %(fqdn)s
X-Git-Repo: %(repo_shortname)s
X-Git-Refname: %(refname)s
X-Git-Reftype: %(refname_type)s
@@ -221,6 +228,7 @@ how to provide full information about this reference change.
REVISION_HEADER_TEMPLATE = """\
+Date: %(send_date)s
To: %(recipients)s
Subject: %(emailprefix)s%(num)02d/%(tot)02d: %(oneline)s
MIME-Version: 1.0
@@ -230,6 +238,7 @@ From: %(fromaddr)s
Reply-To: %(reply_to)s
In-Reply-To: %(reply_to_msgid)s
References: %(reply_to_msgid)s
+X-Git-Host: %(fqdn)s
X-Git-Repo: %(repo_shortname)s
X-Git-Refname: %(refname)s
X-Git-Reftype: %(refname_type)s
@@ -263,13 +272,43 @@ class ConfigurationException(Exception):
pass
+# The "git" program (this could be changed to include a full path):
+GIT_EXECUTABLE = 'git'
+
+
+# How "git" should be invoked (including global arguments), as a list
+# of words. This variable is usually initialized automatically by
+# read_git_output() via choose_git_command(), but if a value is set
+# here then it will be used unconditionally.
+GIT_CMD = None
+
+
+def choose_git_command():
+ """Decide how to invoke git, and record the choice in GIT_CMD."""
+
+ global GIT_CMD
+
+ if GIT_CMD is None:
+ try:
+ # Check to see whether the "-c" option is accepted (it was
+ # only added in Git 1.7.2). We don't actually use the
+ # output of "git --version", though if we needed more
+ # specific version information this would be the place to
+ # do it.
+ cmd = [GIT_EXECUTABLE, '-c', 'foo.bar=baz', '--version']
+ read_output(cmd)
+ GIT_CMD = [GIT_EXECUTABLE, '-c', 'i18n.logoutputencoding=%s' % (ENCODING,)]
+ except CommandError:
+ GIT_CMD = [GIT_EXECUTABLE]
+
+
def read_git_output(args, input=None, keepends=False, **kw):
"""Read the output of a Git command."""
- return read_output(
- ['git', '-c', 'i18n.logoutputencoding=%s' % (ENCODING,)] + args,
- input=input, keepends=keepends, **kw
- )
+ if GIT_CMD is None:
+ choose_git_command()
+
+ return read_output(GIT_CMD + args, input=input, keepends=keepends, **kw)
def read_output(cmd, input=None, keepends=False, **kw):
@@ -297,6 +336,31 @@ def read_git_lines(args, keepends=False, **kw):
return read_git_output(args, keepends=True, **kw).splitlines(keepends)
+def header_encode(text, header_name=None):
+ """Encode and line-wrap the value of an email header field."""
+
+ try:
+ if isinstance(text, str):
+ text = text.decode(ENCODING, 'replace')
+ return Header(text, header_name=header_name).encode()
+ except UnicodeEncodeError:
+ return Header(text, header_name=header_name, charset=CHARSET,
+ errors='replace').encode()
+
+
+def addr_header_encode(text, header_name=None):
+ """Encode and line-wrap the value of an email header field containing
+ email addresses."""
+
+ return Header(
+ ', '.join(
+ formataddr((header_encode(name), emailaddr))
+ for name, emailaddr in getaddresses([text])
+ ),
+ header_name=header_name
+ ).encode()
+
+
class Config(object):
def __init__(self, section, git_config=None):
"""Represent a section of the git configuration.
@@ -578,11 +642,11 @@ class Change(object):
% (e.args[0], line,)
)
else:
- try:
- h = Header(value, header_name=name)
- except UnicodeDecodeError:
- h = Header(value, header_name=name, charset=CHARSET, errors='replace')
- for splitline in ('%s: %s\n' % (name, h.encode(),)).splitlines(True):
+ if name.lower() in ADDR_HEADERS:
+ value = addr_header_encode(value, name)
+ else:
+ value = header_encode(value, name)
+ for splitline in ('%s: %s\n' % (name, value)).splitlines(True):
yield splitline
def generate_email_header(self):
@@ -616,15 +680,19 @@ class Change(object):
raise NotImplementedError()
- def generate_email(self, push, body_filter=None):
+ def generate_email(self, push, body_filter=None, extra_header_values={}):
"""Generate an email describing this change.
Iterate over the lines (including the header lines) of an
email describing this change. If body_filter is not None,
then use it to filter the lines that are intended for the
- email body."""
+ email body.
+
+ The extra_header_values field is received as a dict and not as
+ **kwargs, to allow passing other keyword arguments in the
+ future (e.g. passing extra values to generate_email_intro()"""
- for line in self.generate_email_header():
+ for line in self.generate_email_header(**extra_header_values):
yield line
yield '\n'
for line in self.generate_email_intro():
@@ -680,8 +748,10 @@ class Revision(Change):
return values
- def generate_email_header(self):
- for line in self.expand_header_lines(REVISION_HEADER_TEMPLATE):
+ def generate_email_header(self, **extra_values):
+ for line in self.expand_header_lines(
+ REVISION_HEADER_TEMPLATE, **extra_values
+ ):
yield line
def generate_email_intro(self):
@@ -692,11 +762,7 @@ class Revision(Change):
"""Show this revision."""
return read_git_lines(
- [
- 'log', '-C',
- '--stat', '-p', '--cc',
- '-1', self.rev.sha1,
- ],
+ ['log'] + self.environment.commitlogopts + ['-1', self.rev.sha1],
keepends=True,
)
@@ -800,6 +866,7 @@ class ReferenceChange(Change):
self.msgid = make_msgid()
self.diffopts = environment.diffopts
self.logopts = environment.logopts
+ self.commitlogopts = environment.commitlogopts
self.showlog = environment.refchange_showlog
def _compute_values(self):
@@ -835,9 +902,12 @@ class ReferenceChange(Change):
}[self.change_type]
return self.expand(template)
- def generate_email_header(self):
+ def generate_email_header(self, **extra_values):
+ if 'subject' not in extra_values:
+ extra_values['subject'] = self.get_subject()
+
for line in self.expand_header_lines(
- REFCHANGE_HEADER_TEMPLATE, subject=self.get_subject(),
+ REFCHANGE_HEADER_TEMPLATE, **extra_values
):
yield line
@@ -1273,7 +1343,7 @@ class Mailer(object):
class SendMailer(Mailer):
- """Send emails using 'sendmail -t'."""
+ """Send emails using 'sendmail -oi -t'."""
SENDMAIL_CANDIDATES = [
'/usr/sbin/sendmail',
@@ -1302,7 +1372,7 @@ class SendMailer(Mailer):
if command:
self.command = command[:]
else:
- self.command = [self.find_sendmail(), '-t']
+ self.command = [self.find_sendmail(), '-oi', '-t']
if envelopesender:
self.command.extend(['-f', envelopesender])
@@ -1495,6 +1565,12 @@ class Environment(object):
'git log' when generating the detailed log for a set of
commits (see refchange_showlog)
+ commitlogopts (list of strings)
+
+ The options that should be passed to 'git log' for each
+ commit mail. The value should be a list of strings
+ representing words to be passed to the command.
+
"""
REPO_NAME_RE = re.compile(r'^(?P<name>.+?)(?:\.git)$')
@@ -1506,6 +1582,7 @@ class Environment(object):
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
self.logopts = []
self.refchange_showlog = False
+ self.commitlogopts = ['-C', '--stat', '-p', '--cc']
self.COMPUTED_KEYS = [
'administrator',
@@ -1672,6 +1749,10 @@ class ConfigOptionsEnvironmentMixin(ConfigEnvironmentMixin):
if logopts is not None:
self.logopts = shlex.split(logopts)
+ commitlogopts = config.get('commitlogopts')
+ if commitlogopts is not None:
+ self.commitlogopts = shlex.split(commitlogopts)
+
reply_to = config.get('replyTo')
self.__reply_to_refchange = config.get('replyToRefchange', default=reply_to)
if (
@@ -1829,6 +1910,47 @@ class ConfigMaxlinesEnvironmentMixin(
)
+class FQDNEnvironmentMixin(Environment):
+ """A mixin that sets the host's FQDN to its constructor argument."""
+
+ def __init__(self, fqdn, **kw):
+ super(FQDNEnvironmentMixin, self).__init__(**kw)
+ self.COMPUTED_KEYS += ['fqdn']
+ self.__fqdn = fqdn
+
+ def get_fqdn(self):
+ """Return the fully-qualified domain name for this host.
+
+ Return None if it is unavailable or unwanted."""
+
+ return self.__fqdn
+
+
+class ConfigFQDNEnvironmentMixin(
+ ConfigEnvironmentMixin,
+ FQDNEnvironmentMixin,
+ ):
+ """Read the FQDN from the config."""
+
+ def __init__(self, config, **kw):
+ fqdn = config.get('fqdn')
+ super(ConfigFQDNEnvironmentMixin, self).__init__(
+ config=config,
+ fqdn=fqdn,
+ **kw
+ )
+
+
+class ComputeFQDNEnvironmentMixin(FQDNEnvironmentMixin):
+ """Get the FQDN by calling socket.getfqdn()."""
+
+ def __init__(self, **kw):
+ super(ComputeFQDNEnvironmentMixin, self).__init__(
+ fqdn=socket.getfqdn(),
+ **kw
+ )
+
+
class PusherDomainEnvironmentMixin(ConfigEnvironmentMixin):
"""Deduce pusher_email from pusher by appending an emaildomain."""
@@ -1861,6 +1983,10 @@ class StaticRecipientsEnvironmentMixin(Environment):
# actual *contents* of the change being reported, we only
# choose based on the *type* of the change. Therefore we can
# compute them once and for all:
+ if not (refchange_recipients
+ or announce_recipients
+ or revision_recipients):
+ raise ConfigurationException('No email recipients configured!')
self.__refchange_recipients = refchange_recipients
self.__announce_recipients = announce_recipients
self.__revision_recipients = revision_recipients
@@ -1911,17 +2037,8 @@ class ConfigRecipientsEnvironmentMixin(
retval = config.get_recipients(name)
if retval is not None:
return retval
- if len(names) == 1:
- hint = 'Please set "%s.%s"' % (config.section, name)
else:
- hint = (
- 'Please set one of the following:\n "%s"'
- % ('"\n "'.join('%s.%s' % (config.section, name) for name in names))
- )
-
- raise ConfigurationException(
- 'The list of recipients for %s is not configured.\n%s' % (names[0], hint)
- )
+ return ''
class ProjectdescEnvironmentMixin(Environment):
@@ -1956,6 +2073,7 @@ class GenericEnvironmentMixin(Environment):
class GenericEnvironment(
ProjectdescEnvironmentMixin,
ConfigMaxlinesEnvironmentMixin,
+ ComputeFQDNEnvironmentMixin,
ConfigFilterLinesEnvironmentMixin,
ConfigRecipientsEnvironmentMixin,
PusherDomainEnvironmentMixin,
@@ -1980,9 +2098,27 @@ class GitoliteEnvironmentMixin(Environment):
return self.osenv.get('GL_USER', 'unknown user')
+class IncrementalDateTime(object):
+ """Simple wrapper to give incremental date/times.
+
+ Each call will result in a date/time a second later than the
+ previous call. This can be used to falsify email headers, to
+ increase the likelihood that email clients sort the emails
+ correctly."""
+
+ def __init__(self):
+ self.time = time.time()
+
+ def next(self):
+ formatted = formatdate(self.time, True)
+ self.time += 1
+ return formatted
+
+
class GitoliteEnvironment(
ProjectdescEnvironmentMixin,
ConfigMaxlinesEnvironmentMixin,
+ ComputeFQDNEnvironmentMixin,
ConfigFilterLinesEnvironmentMixin,
ConfigRecipientsEnvironmentMixin,
PusherDomainEnvironmentMixin,
@@ -2187,6 +2323,7 @@ class Push(object):
# guarantee that one (and only one) email is generated for
# each new commit.
unhandled_sha1s = set(self.get_new_commits())
+ send_date = IncrementalDateTime()
for change in self.changes:
# Check if we've got anyone to send to
if not change.recipients:
@@ -2197,7 +2334,11 @@ class Push(object):
)
else:
sys.stderr.write('Sending notification emails to: %s\n' % (change.recipients,))
- mailer.send(change.generate_email(self, body_filter), change.recipients)
+ extra_values = {'send_date' : send_date.next()}
+ mailer.send(
+ change.generate_email(self, body_filter, extra_values),
+ change.recipients,
+ )
sha1s = []
for sha1 in reversed(list(self.get_new_commits(change))):
@@ -2217,7 +2358,11 @@ class Push(object):
for (num, sha1) in enumerate(sha1s):
rev = Revision(change, GitObject(sha1), num=num+1, tot=len(sha1s))
if rev.recipients:
- mailer.send(rev.generate_email(self, body_filter), rev.recipients)
+ extra_values = {'send_date' : send_date.next()}
+ mailer.send(
+ rev.generate_email(self, body_filter, extra_values),
+ rev.recipients,
+ )
# Consistency check:
if unhandled_sha1s:
@@ -2288,6 +2433,7 @@ def choose_environment(config, osenv=None, env=None, recipients=None):
environment_mixins = [
ProjectdescEnvironmentMixin,
ConfigMaxlinesEnvironmentMixin,
+ ComputeFQDNEnvironmentMixin,
ConfigFilterLinesEnvironmentMixin,
PusherDomainEnvironmentMixin,
ConfigOptionsEnvironmentMixin,
diff --git a/contrib/hooks/multimail/post-receive b/contrib/hooks/multimail/post-receive
index 93ebb437d1..4d46828ba5 100755
--- a/contrib/hooks/multimail/post-receive
+++ b/contrib/hooks/multimail/post-receive
@@ -66,10 +66,10 @@ mailer = git_multimail.choose_mailer(config, environment)
# Alternatively, you may hardcode the mailer using code like one of
# the following:
-# Use "/usr/sbin/sendmail -t" to send emails. The envelopesender
+# Use "/usr/sbin/sendmail -oi -t" to send emails. The envelopesender
# argument is optional:
#mailer = git_multimail.SendMailer(
-# command=['/usr/sbin/sendmail', '-t'],
+# command=['/usr/sbin/sendmail', '-oi', '-t'],
# envelopesender='git-repo@example.com',
# )
diff --git a/contrib/hooks/post-receive-email b/contrib/hooks/post-receive-email
index 8ee410f843..8747b84334 100755
--- a/contrib/hooks/post-receive-email
+++ b/contrib/hooks/post-receive-email
@@ -22,7 +22,6 @@
# For example, on debian the hook is stored in
# /usr/share/git-core/contrib/hooks/post-receive-email:
#
-# chmod a+x post-receive-email
# cd /path/to/your/repository.git
# ln -sf /usr/share/git-core/contrib/hooks/post-receive-email hooks/post-receive
#
diff --git a/contrib/hooks/pre-auto-gc-battery b/contrib/hooks/pre-auto-gc-battery
index 1f914c94aa..9d0c2d1990 100644..100755
--- a/contrib/hooks/pre-auto-gc-battery
+++ b/contrib/hooks/pre-auto-gc-battery
@@ -13,7 +13,6 @@
# For example, if the hook is stored in
# /usr/share/git-core/contrib/hooks/pre-auto-gc-battery:
#
-# chmod a+x pre-auto-gc-battery
# cd /path/to/your/repository.git
# ln -sf /usr/share/git-core/contrib/hooks/pre-auto-gc-battery \
# hooks/pre-auto-gc
diff --git a/contrib/hooks/setgitperms.perl b/contrib/hooks/setgitperms.perl
index 2770a1b1d2..2770a1b1d2 100644..100755
--- a/contrib/hooks/setgitperms.perl
+++ b/contrib/hooks/setgitperms.perl
diff --git a/contrib/hooks/update-paranoid b/contrib/hooks/update-paranoid
index d18b317b2f..d18b317b2f 100644..100755
--- a/contrib/hooks/update-paranoid
+++ b/contrib/hooks/update-paranoid
diff --git a/contrib/mw-to-git/Makefile b/contrib/mw-to-git/Makefile
index f206f9655b..a4b6f7a2cd 100644
--- a/contrib/mw-to-git/Makefile
+++ b/contrib/mw-to-git/Makefile
@@ -18,9 +18,13 @@ SCRIPT_PERL+=git-mw.perl
GIT_ROOT_DIR=../..
HERE=contrib/mw-to-git/
+INSTALL = install
+
SCRIPT_PERL_FULL=$(patsubst %,$(HERE)/%,$(SCRIPT_PERL))
INSTLIBDIR=$(shell $(MAKE) -C $(GIT_ROOT_DIR)/perl \
-s --no-print-directory instlibdir)
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+INSTLIBDIR_SQ = $(subst ','\'',$(INSTLIBDIR))
all: build
@@ -30,7 +34,9 @@ test: all
check: perlcritic test
install_pm:
- install $(GIT_MEDIAWIKI_PM) $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM)
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(INSTLIBDIR_SQ)/Git'
+ $(INSTALL) -m 644 $(GIT_MEDIAWIKI_PM) \
+ '$(DESTDIR_SQ)$(INSTLIBDIR_SQ)/$(GIT_MEDIAWIKI_PM)'
build:
$(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
@@ -43,7 +49,6 @@ install: install_pm
clean:
$(MAKE) -C $(GIT_ROOT_DIR) SCRIPT_PERL="$(SCRIPT_PERL_FULL)" \
clean-perl-script
- rm $(INSTLIBDIR)/$(GIT_MEDIAWIKI_PM)
perlcritic:
perlcritic -5 $(SCRIPT_PERL)
diff --git a/contrib/p4import/README b/contrib/p4import/README
deleted file mode 100644
index b9892b6793..0000000000
--- a/contrib/p4import/README
+++ /dev/null
@@ -1 +0,0 @@
-Please see contrib/fast-import/git-p4 for a better Perforce importer.
diff --git a/contrib/p4import/git-p4import.py b/contrib/p4import/git-p4import.py
deleted file mode 100644
index 593d6a0682..0000000000
--- a/contrib/p4import/git-p4import.py
+++ /dev/null
@@ -1,365 +0,0 @@
-#!/usr/bin/env python
-#
-# This tool is copyright (c) 2006, Sean Estabrooks.
-# It is released under the Gnu Public License, version 2.
-#
-# Import Perforce branches into Git repositories.
-# Checking out the files is done by calling the standard p4
-# client which you must have properly configured yourself
-#
-
-import marshal
-import os
-import sys
-import time
-import getopt
-
-if sys.hexversion < 0x02020000:
- # The behavior of the marshal module changed significantly in 2.2
- sys.stderr.write("git-p4import.py: requires Python 2.2 or later.\n")
- sys.exit(1)
-
-from signal import signal, \
- SIGPIPE, SIGINT, SIG_DFL, \
- default_int_handler
-
-signal(SIGPIPE, SIG_DFL)
-s = signal(SIGINT, SIG_DFL)
-if s != default_int_handler:
- signal(SIGINT, s)
-
-def die(msg, *args):
- for a in args:
- msg = "%s %s" % (msg, a)
- print "git-p4import fatal error:", msg
- sys.exit(1)
-
-def usage():
- print "USAGE: git-p4import [-q|-v] [--authors=<file>] [-t <timezone>] [//p4repo/path <branch>]"
- sys.exit(1)
-
-verbosity = 1
-logfile = "/dev/null"
-ignore_warnings = False
-stitch = 0
-tagall = True
-
-def report(level, msg, *args):
- global verbosity
- global logfile
- for a in args:
- msg = "%s %s" % (msg, a)
- fd = open(logfile, "a")
- fd.writelines(msg)
- fd.close()
- if level <= verbosity:
- print msg
-
-class p4_command:
- def __init__(self, _repopath):
- try:
- global logfile
- self.userlist = {}
- if _repopath[-1] == '/':
- self.repopath = _repopath[:-1]
- else:
- self.repopath = _repopath
- if self.repopath[-4:] != "/...":
- self.repopath= "%s/..." % self.repopath
- f=os.popen('p4 -V 2>>%s'%logfile, 'rb')
- a = f.readlines()
- if f.close():
- raise
- except:
- die("Could not find the \"p4\" command")
-
- def p4(self, cmd, *args):
- global logfile
- cmd = "%s %s" % (cmd, ' '.join(args))
- report(2, "P4:", cmd)
- f=os.popen('p4 -G %s 2>>%s' % (cmd,logfile), 'rb')
- list = []
- while 1:
- try:
- list.append(marshal.load(f))
- except EOFError:
- break
- self.ret = f.close()
- return list
-
- def sync(self, id, force=False, trick=False, test=False):
- if force:
- ret = self.p4("sync -f %s@%s"%(self.repopath, id))[0]
- elif trick:
- ret = self.p4("sync -k %s@%s"%(self.repopath, id))[0]
- elif test:
- ret = self.p4("sync -n %s@%s"%(self.repopath, id))[0]
- else:
- ret = self.p4("sync %s@%s"%(self.repopath, id))[0]
- if ret['code'] == "error":
- data = ret['data'].upper()
- if data.find('VIEW') > 0:
- die("Perforce reports %s is not in client view"% self.repopath)
- elif data.find('UP-TO-DATE') < 0:
- die("Could not sync files from perforce", self.repopath)
-
- def changes(self, since=0):
- try:
- list = []
- for rec in self.p4("changes %s@%s,#head" % (self.repopath, since+1)):
- list.append(rec['change'])
- list.reverse()
- return list
- except:
- return []
-
- def authors(self, filename):
- f=open(filename)
- for l in f.readlines():
- self.userlist[l[:l.find('=')].rstrip()] = \
- (l[l.find('=')+1:l.find('<')].rstrip(),l[l.find('<')+1:l.find('>')])
- f.close()
- for f,e in self.userlist.items():
- report(2, f, ":", e[0], " <", e[1], ">")
-
- def _get_user(self, id):
- if not self.userlist.has_key(id):
- try:
- user = self.p4("users", id)[0]
- self.userlist[id] = (user['FullName'], user['Email'])
- except:
- self.userlist[id] = (id, "")
- return self.userlist[id]
-
- def _format_date(self, ticks):
- symbol='+'
- name = time.tzname[0]
- offset = time.timezone
- if ticks[8]:
- name = time.tzname[1]
- offset = time.altzone
- if offset < 0:
- offset *= -1
- symbol = '-'
- localo = "%s%02d%02d %s" % (symbol, offset / 3600, offset % 3600, name)
- tickso = time.strftime("%a %b %d %H:%M:%S %Y", ticks)
- return "%s %s" % (tickso, localo)
-
- def where(self):
- try:
- return self.p4("where %s" % self.repopath)[-1]['path']
- except:
- return ""
-
- def describe(self, num):
- desc = self.p4("describe -s", num)[0]
- self.msg = desc['desc']
- self.author, self.email = self._get_user(desc['user'])
- self.date = self._format_date(time.localtime(long(desc['time'])))
- return self
-
-class git_command:
- def __init__(self):
- try:
- self.version = self.git("--version")[0][12:].rstrip()
- except:
- die("Could not find the \"git\" command")
- try:
- self.gitdir = self.get_single("rev-parse --git-dir")
- report(2, "gdir:", self.gitdir)
- except:
- die("Not a git repository... did you forget to \"git init\" ?")
- try:
- self.cdup = self.get_single("rev-parse --show-cdup")
- if self.cdup != "":
- os.chdir(self.cdup)
- self.topdir = os.getcwd()
- report(2, "topdir:", self.topdir)
- except:
- die("Could not find top git directory")
-
- def git(self, cmd):
- global logfile
- report(2, "GIT:", cmd)
- f=os.popen('git %s 2>>%s' % (cmd,logfile), 'rb')
- r=f.readlines()
- self.ret = f.close()
- return r
-
- def get_single(self, cmd):
- return self.git(cmd)[0].rstrip()
-
- def current_branch(self):
- try:
- testit = self.git("rev-parse --verify HEAD")[0]
- return self.git("symbolic-ref HEAD")[0][11:].rstrip()
- except:
- return None
-
- def get_config(self, variable):
- try:
- return self.git("config --get %s" % variable)[0].rstrip()
- except:
- return None
-
- def set_config(self, variable, value):
- try:
- self.git("config %s %s"%(variable, value) )
- except:
- die("Could not set %s to " % variable, value)
-
- def make_tag(self, name, head):
- self.git("tag -f %s %s"%(name,head))
-
- def top_change(self, branch):
- try:
- a=self.get_single("name-rev --tags refs/heads/%s" % branch)
- loc = a.find(' tags/') + 6
- if a[loc:loc+3] != "p4/":
- raise
- return int(a[loc+3:][:-2])
- except:
- return 0
-
- def update_index(self):
- self.git("ls-files -m -d -o -z | git update-index --add --remove -z --stdin")
-
- def checkout(self, branch):
- self.git("checkout %s" % branch)
-
- def repoint_head(self, branch):
- self.git("symbolic-ref HEAD refs/heads/%s" % branch)
-
- def remove_files(self):
- self.git("ls-files | xargs rm")
-
- def clean_directories(self):
- self.git("clean -d")
-
- def fresh_branch(self, branch):
- report(1, "Creating new branch", branch)
- self.git("ls-files | xargs rm")
- os.remove(".git/index")
- self.repoint_head(branch)
- self.git("clean -d")
-
- def basedir(self):
- return self.topdir
-
- def commit(self, author, email, date, msg, id):
- self.update_index()
- fd=open(".msg", "w")
- fd.writelines(msg)
- fd.close()
- try:
- current = self.get_single("rev-parse --verify HEAD")
- head = "-p HEAD"
- except:
- current = ""
- head = ""
- tree = self.get_single("write-tree")
- for r,l in [('DATE',date),('NAME',author),('EMAIL',email)]:
- os.environ['GIT_AUTHOR_%s'%r] = l
- os.environ['GIT_COMMITTER_%s'%r] = l
- commit = self.get_single("commit-tree %s %s < .msg" % (tree,head))
- os.remove(".msg")
- self.make_tag("p4/%s"%id, commit)
- self.git("update-ref HEAD %s %s" % (commit, current) )
-
-try:
- opts, args = getopt.getopt(sys.argv[1:], "qhvt:",
- ["authors=","help","stitch=","timezone=","log=","ignore","notags"])
-except getopt.GetoptError:
- usage()
-
-for o, a in opts:
- if o == "-q":
- verbosity = 0
- if o == "-v":
- verbosity += 1
- if o in ("--log"):
- logfile = a
- if o in ("--notags"):
- tagall = False
- if o in ("-h", "--help"):
- usage()
- if o in ("--ignore"):
- ignore_warnings = True
-
-git = git_command()
-branch=git.current_branch()
-
-for o, a in opts:
- if o in ("-t", "--timezone"):
- git.set_config("perforce.timezone", a)
- if o in ("--stitch"):
- git.set_config("perforce.%s.path" % branch, a)
- stitch = 1
-
-if len(args) == 2:
- branch = args[1]
- git.checkout(branch)
- if branch == git.current_branch():
- die("Branch %s already exists!" % branch)
- report(1, "Setting perforce to ", args[0])
- git.set_config("perforce.%s.path" % branch, args[0])
-elif len(args) != 0:
- die("You must specify the perforce //depot/path and git branch")
-
-p4path = git.get_config("perforce.%s.path" % branch)
-if p4path == None:
- die("Do not know Perforce //depot/path for git branch", branch)
-
-p4 = p4_command(p4path)
-
-for o, a in opts:
- if o in ("-a", "--authors"):
- p4.authors(a)
-
-localdir = git.basedir()
-if p4.where()[:len(localdir)] != localdir:
- report(1, "**WARNING** Appears p4 client is misconfigured")
- report(1, " for sync from %s to %s" % (p4.repopath, localdir))
- if ignore_warnings != True:
- die("Reconfigure or use \"--ignore\" on command line")
-
-if stitch == 0:
- top = git.top_change(branch)
-else:
- top = 0
-changes = p4.changes(top)
-count = len(changes)
-if count == 0:
- report(1, "Already up to date...")
- sys.exit(0)
-
-ptz = git.get_config("perforce.timezone")
-if ptz:
- report(1, "Setting timezone to", ptz)
- os.environ['TZ'] = ptz
- time.tzset()
-
-if stitch == 1:
- git.remove_files()
- git.clean_directories()
- p4.sync(changes[0], force=True)
-elif top == 0 and branch != git.current_branch():
- p4.sync(changes[0], test=True)
- report(1, "Creating new initial commit");
- git.fresh_branch(branch)
- p4.sync(changes[0], force=True)
-else:
- p4.sync(changes[0], trick=True)
-
-report(1, "processing %s changes from p4 (%s) to git (%s)" % (count, p4.repopath, branch))
-for id in changes:
- report(1, "Importing changeset", id)
- change = p4.describe(id)
- p4.sync(id)
- if tagall :
- git.commit(change.author, change.email, change.date, change.msg, id)
- else:
- git.commit(change.author, change.email, change.date, change.msg, "import")
- if stitch == 1:
- git.clean_directories()
- stitch = 0
diff --git a/contrib/p4import/git-p4import.txt b/contrib/p4import/git-p4import.txt
deleted file mode 100644
index 9967587fe6..0000000000
--- a/contrib/p4import/git-p4import.txt
+++ /dev/null
@@ -1,167 +0,0 @@
-git-p4import(1)
-===============
-
-NAME
-----
-git-p4import - Import a Perforce repository into git
-
-
-SYNOPSIS
---------
-[verse]
-`git-p4import` [-q|-v] [--notags] [--authors <file>] [-t <timezone>]
- <//p4repo/path> <branch>
-`git-p4import` --stitch <//p4repo/path>
-`git-p4import`
-
-
-DESCRIPTION
------------
-Import a Perforce repository into an existing git repository. When
-a <//p4repo/path> and <branch> are specified a new branch with the
-given name will be created and the initial import will begin.
-
-Once the initial import is complete you can do an incremental import
-of new commits from the Perforce repository. You do this by checking
-out the appropriate git branch and then running `git-p4import` without
-any options.
-
-The standard p4 client is used to communicate with the Perforce
-repository; it must be configured correctly in order for `git-p4import`
-to operate (see below).
-
-
-OPTIONS
--------
--q::
- Do not display any progress information.
-
--v::
- Give extra progress information.
-
-\--authors::
- Specify an authors file containing a mapping of Perforce user
- ids to full names and email addresses (see Notes below).
-
-\--notags::
- Do not create a tag for each imported commit.
-
-\--stitch::
- Import the contents of the given perforce branch into the
- currently checked out git branch.
-
-\--log::
- Store debugging information in the specified file.
-
--t::
- Specify that the remote repository is in the specified timezone.
- Timezone must be in the format "US/Pacific" or "Europe/London"
- etc. You only need to specify this once, it will be saved in
- the git config file for the repository.
-
-<//p4repo/path>::
- The Perforce path that will be imported into the specified branch.
-
-<branch>::
- The new branch that will be created to hold the Perforce imports.
-
-
-P4 Client
----------
-You must make the `p4` client command available in your $PATH and
-configure it to communicate with the target Perforce repository.
-Typically this means you must set the "$P4PORT" and "$P4CLIENT"
-environment variables.
-
-You must also configure a `p4` client "view" which maps the Perforce
-branch into the top level of your git repository, for example:
-
-------------
-Client: myhost
-
-Root: /home/sean/import
-
-Options: noallwrite clobber nocompress unlocked modtime rmdir
-
-View:
- //public/jam/... //myhost/jam/...
-------------
-
-With the above `p4` client setup, you could import the "jam"
-perforce branch into a branch named "jammy", like so:
-
-------------
-$ mkdir -p /home/sean/import/jam
-$ cd /home/sean/import/jam
-$ git init
-$ git p4import //public/jam jammy
-------------
-
-
-Multiple Branches
------------------
-Note that by creating multiple "views" you can use `git-p4import`
-to import additional branches into the same git repository.
-However, the `p4` client has a limitation in that it silently
-ignores all but the last "view" that maps into the same local
-directory. So the following will *not* work:
-
-------------
-View:
- //public/jam/... //myhost/jam/...
- //public/other/... //myhost/jam/...
- //public/guest/... //myhost/jam/...
-------------
-
-If you want more than one Perforce branch to be imported into the
-same directory you must employ a workaround. A simple option is
-to adjust your `p4` client before each import to only include a
-single view.
-
-Another option is to create multiple symlinks locally which all
-point to the same directory in your git repository and then use
-one per "view" instead of listing the actual directory.
-
-
-Tags
-----
-A git tag of the form p4/xx is created for every change imported from
-the Perforce repository where xx is the Perforce changeset number.
-Therefore after the import you can use git to access any commit by its
-Perforce number, e.g. git show p4/327.
-
-The tag associated with the HEAD commit is also how `git-p4import`
-determines if there are new changes to incrementally import from the
-Perforce repository.
-
-If you import from a repository with many thousands of changes
-you will have an equal number of p4/xxxx git tags. Git tags can
-be expensive in terms of disk space and repository operations.
-If you don't need to perform further incremental imports, you
-may delete the tags.
-
-
-Notes
------
-You can interrupt the import (e.g. ctrl-c) at any time and restart it
-without worry.
-
-Author information is automatically determined by querying the
-Perforce "users" table using the id associated with each change.
-However, if you want to manually supply these mappings you can do
-so with the "--authors" option. It accepts a file containing a list
-of mappings with each line containing one mapping in the format:
-
-------------
- perforce_id = Full Name <email@address.com>
-------------
-
-
-Author
-------
-Written by Sean Estabrooks <seanlkml@sympatico.ca>
-
-
-GIT
----
-Part of the gitlink:git[7] suite
diff --git a/contrib/remote-helpers/git-remote-bzr b/contrib/remote-helpers/git-remote-bzr
index 054161ae21..9abb58e6ab 100755
--- a/contrib/remote-helpers/git-remote-bzr
+++ b/contrib/remote-helpers/git-remote-bzr
@@ -44,8 +44,8 @@ import StringIO
import atexit, shutil, hashlib, urlparse, subprocess
NAME_RE = re.compile('^([^<>]+)')
-AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
-EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
+AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
+EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
RAW_AUTHOR_RE = re.compile('^(\w+) (.+)? <(.*)> (\d+) ([+-]\d+)')
def die(msg, *args):
@@ -193,8 +193,7 @@ def fixup_user(user):
else:
m = EMAIL_RE.match(user)
if m:
- name = m.group(1)
- mail = m.group(2)
+ mail = m.group(1)
else:
m = NAME_RE.match(user)
if m:
@@ -619,10 +618,12 @@ def parse_commit(parser):
files[path] = f
committer, date, tz = committer
+ author, _, _ = author
parents = [mark_to_rev(p) for p in parents]
revid = bzrlib.generate_ids.gen_revision_id(committer, date)
props = {}
props['branch-nick'] = branch.nick
+ props['authors'] = author
mtree = CustomTree(branch, revid, parents, files)
changes = mtree.iter_changes()
@@ -685,7 +686,8 @@ def do_export(parser):
peer = bzrlib.branch.Branch.open(peers[name],
possible_transports=transports)
try:
- peer.bzrdir.push_branch(branch, revision_id=revid)
+ peer.bzrdir.push_branch(branch, revision_id=revid,
+ overwrite=force)
except bzrlib.errors.DivergedBranches:
print "error %s non-fast forward" % ref
continue
@@ -719,8 +721,32 @@ def do_capabilities(parser):
print "*import-marks %s" % path
print "*export-marks %s" % path
+ print "option"
print
+class InvalidOptionValue(Exception):
+ pass
+
+def get_bool_option(val):
+ if val == 'true':
+ return True
+ elif val == 'false':
+ return False
+ else:
+ raise InvalidOptionValue()
+
+def do_option(parser):
+ global force
+ opt, val = parser[1:3]
+ try:
+ if opt == 'force':
+ force = get_bool_option(val)
+ print 'ok'
+ else:
+ print 'unsupported'
+ except InvalidOptionValue:
+ print "error '%s' is not a valid value for option '%s'" % (val, opt)
+
def ref_is_valid(name):
return not True in [c in name for c in '~^: \\']
@@ -760,7 +786,7 @@ def clone(path, remote_branch):
def get_remote_branch(name):
remote_branch = bzrlib.branch.Branch.open(branches[name],
possible_transports=transports)
- if isinstance(remote_branch.user_transport, bzrlib.transport.local.LocalTransport):
+ if isinstance(remote_branch.bzrdir.root_transport, bzrlib.transport.local.LocalTransport):
return remote_branch
branch_path = os.path.join(dirname, 'clone', name)
@@ -843,7 +869,7 @@ def get_repo(url, alias):
if not wanted:
try:
repo = origin.open_repository()
- if not repo.user_transport.listable():
+ if not repo.bzrdir.root_transport.listable():
# this repository is not usable for us
raise bzrlib.errors.NoRepositoryPresent(repo.bzrdir)
except bzrlib.errors.NoRepositoryPresent:
@@ -883,6 +909,17 @@ def main(args):
global is_tmp
global branches, peers
global transports
+ global force
+
+ marks = None
+ is_tmp = False
+ gitdir = os.environ.get('GIT_DIR', None)
+
+ if len(args) < 3:
+ die('Not enough arguments.')
+
+ if not gitdir:
+ die('GIT_DIR not set')
alias = args[1]
url = args[2]
@@ -892,19 +929,16 @@ def main(args):
blob_marks = {}
parsed_refs = {}
files_cache = {}
- marks = None
branches = {}
peers = {}
transports = []
+ force = False
if alias[5:] == url:
is_tmp = True
alias = hashlib.sha1(alias).hexdigest()
- else:
- is_tmp = False
prefix = 'refs/bzr/%s' % alias
- gitdir = os.environ['GIT_DIR']
dirname = os.path.join(gitdir, 'bzr', alias)
if not is_tmp:
@@ -931,6 +965,8 @@ def main(args):
do_import(parser)
elif parser.check('export'):
do_export(parser)
+ elif parser.check('option'):
+ do_option(parser)
else:
die('unhandled command: %s' % line)
sys.stdout.flush()
diff --git a/contrib/remote-helpers/git-remote-hg b/contrib/remote-helpers/git-remote-hg
index c6026b9bed..34cda02759 100755
--- a/contrib/remote-helpers/git-remote-hg
+++ b/contrib/remote-helpers/git-remote-hg
@@ -51,8 +51,8 @@ import time as ptime
#
NAME_RE = re.compile('^([^<>]+)')
-AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
-EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
+AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
+EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
@@ -260,6 +260,7 @@ class Parser:
return (user, int(date), -tz)
def fix_file_path(path):
+ path = os.path.normpath(path)
if not os.path.isabs(path):
return path
return os.path.relpath(path, '/')
@@ -316,8 +317,7 @@ def fixup_user_git(user):
else:
m = EMAIL_RE.match(user)
if m:
- name = m.group(1)
- mail = m.group(2)
+ mail = m.group(1)
else:
m = NAME_RE.match(user)
if m:
@@ -416,6 +416,9 @@ def get_repo(url, alias):
local_path = os.path.join(dirname, 'clone')
if not os.path.exists(local_path):
hg.share(myui, shared_path, local_path, update=False)
+ else:
+ # make sure the shared path is always up-to-date
+ util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path)
repo = hg.repository(myui, local_path)
try:
@@ -538,7 +541,7 @@ def export_ref(repo, name, kind, head):
print "commit %s" % ref
print "mark :%d" % (note_mark)
- print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
+ print "committer remote-hg <> %d %s" % (ptime.time(), gittz(ptime.timezone))
desc = "Notes for %s\n" % (name)
print "data %d" % (len(desc))
print desc
@@ -641,7 +644,10 @@ def do_list(parser):
print "? refs/heads/branches/%s" % gitref(branch)
for bmark in bmarks:
- print "? refs/heads/%s" % gitref(bmark)
+ if bmarks[bmark].hex() == '0000000000000000000000000000000000000000':
+ warn("Ignoring invalid bookmark '%s'", bmark)
+ else:
+ print "? refs/heads/%s" % gitref(bmark)
for tag, node in repo.tagslist():
if tag == 'tip':
@@ -1165,6 +1171,16 @@ def main(args):
global dry_run
global notes, alias
+ marks = None
+ is_tmp = False
+ gitdir = os.environ.get('GIT_DIR', None)
+
+ if len(args) < 3:
+ die('Not enough arguments.')
+
+ if not gitdir:
+ die('GIT_DIR not set')
+
alias = args[1]
url = args[2]
peer = None
@@ -1185,16 +1201,12 @@ def main(args):
if alias[4:] == url:
is_tmp = True
alias = hashlib.sha1(alias).hexdigest()
- else:
- is_tmp = False
- gitdir = os.environ['GIT_DIR']
dirname = os.path.join(gitdir, 'hg', alias)
branches = {}
bmarks = {}
blob_marks = {}
parsed_refs = {}
- marks = None
parsed_tags = {}
filenodes = {}
fake_bmark = None
diff --git a/contrib/remote-helpers/test-bzr.sh b/contrib/remote-helpers/test-bzr.sh
index 5c50251783..a4656ce412 100755
--- a/contrib/remote-helpers/test-bzr.sh
+++ b/contrib/remote-helpers/test-bzr.sh
@@ -5,7 +5,8 @@
test_description='Test remote-bzr'
-. ./test-lib.sh
+test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
+. "$TEST_DIRECTORY"/test-lib.sh
if ! test_have_prereq PYTHON
then
@@ -65,13 +66,33 @@ test_expect_success 'pushing' '
test_cmp expected actual
'
+test_expect_success 'forced pushing' '
+ (
+ cd gitrepo &&
+ echo three-new >content &&
+ git commit -a --amend -m three-new &&
+ git push -f
+ ) &&
+
+ (
+ cd bzrrepo &&
+ # the forced update overwrites the bzr branch but not the bzr
+ # working directory (it tries to merge instead)
+ bzr revert
+ ) &&
+
+ echo three-new >expected &&
+ cat bzrrepo/content >actual &&
+ test_cmp expected actual
+'
+
test_expect_success 'roundtrip' '
(
cd gitrepo &&
git pull &&
git log --format="%s" -1 origin/master >actual
) &&
- echo three >expected &&
+ echo three-new >expected &&
test_cmp expected actual &&
(cd gitrepo && git push && git pull) &&
@@ -361,7 +382,7 @@ test_expect_success 'strip' '
'
test_expect_success 'export utf-8 authors' '
- test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C && unset GIT_COMMITTER_NAME" &&
+ test_when_finished "rm -rf bzrrepo gitrepo && LC_ALL=C && GIT_COMMITTER_NAME=\"C O Mitter\""
LC_ALL=en_US.UTF-8
export LC_ALL
@@ -378,7 +399,7 @@ test_expect_success 'export utf-8 authors' '
git add content &&
git commit -m one &&
git remote add bzr "bzr::../bzrrepo" &&
- git push bzr
+ git push bzr master
) &&
(
@@ -390,4 +411,28 @@ test_expect_success 'export utf-8 authors' '
test_cmp expected actual
'
+test_expect_success 'push different author' '
+ test_when_finished "rm -rf bzrrepo gitrepo" &&
+
+ bzr init bzrrepo &&
+
+ (
+ git init gitrepo &&
+ cd gitrepo &&
+ echo john >> content &&
+ git add content &&
+ git commit -m john --author "John Doe <jdoe@example.com>" &&
+ git remote add bzr "bzr::../bzrrepo" &&
+ git push bzr master
+ ) &&
+
+ (
+ cd bzrrepo &&
+ bzr log | grep "^author: " > ../actual
+ ) &&
+
+ echo "author: John Doe <jdoe@example.com>" > expected &&
+ test_cmp expected actual
+'
+
test_done
diff --git a/contrib/remote-helpers/test-hg-bidi.sh b/contrib/remote-helpers/test-hg-bidi.sh
index e24c51daad..d86e147d3d 100755
--- a/contrib/remote-helpers/test-hg-bidi.sh
+++ b/contrib/remote-helpers/test-hg-bidi.sh
@@ -8,7 +8,8 @@
test_description='Test bidirectionality of remote-hg'
-. ./test-lib.sh
+test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
+. "$TEST_DIRECTORY"/test-lib.sh
if ! test_have_prereq PYTHON
then
diff --git a/contrib/remote-helpers/test-hg-hg-git.sh b/contrib/remote-helpers/test-hg-hg-git.sh
index 6dcd95d10f..b23909ae6c 100755
--- a/contrib/remote-helpers/test-hg-hg-git.sh
+++ b/contrib/remote-helpers/test-hg-hg-git.sh
@@ -8,7 +8,8 @@
test_description='Test remote-hg output compared to hg-git'
-. ./test-lib.sh
+test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
+. "$TEST_DIRECTORY"/test-lib.sh
if ! test_have_prereq PYTHON
then
diff --git a/contrib/remote-helpers/test-hg.sh b/contrib/remote-helpers/test-hg.sh
index 72f745d63f..7d90056cf3 100755
--- a/contrib/remote-helpers/test-hg.sh
+++ b/contrib/remote-helpers/test-hg.sh
@@ -8,7 +8,8 @@
test_description='Test remote-hg'
-. ./test-lib.sh
+test -n "$TEST_DIRECTORY" || TEST_DIRECTORY=${0%/*}/../../t
+. "$TEST_DIRECTORY"/test-lib.sh
if ! test_have_prereq PYTHON
then
@@ -53,14 +54,14 @@ check_bookmark () {
}
check_push () {
- local expected_ret=$1 ret=0 ref_ret=0 IFS=':'
+ expected_ret=$1 ret=0 ref_ret=0
shift
git push origin "$@" 2>error
ret=$?
cat error
- while read branch kind
+ while IFS=':' read branch kind
do
case "$kind" in
'new')
@@ -82,7 +83,7 @@ check_push () {
test $ref_ret -ne 0 && echo "match for '$branch' failed" && break
done
- if test $expected_ret -ne $ret -o $ref_ret -ne 0
+ if test $expected_ret -ne $ret || test $ref_ret -ne 0
then
return 1
fi
@@ -205,16 +206,17 @@ test_expect_success 'authors' '
>../expected &&
author_test alpha "" "H G Wells <wells@example.com>" &&
- author_test beta "test" "test <unknown>" &&
- author_test beta "test <test@example.com> (comment)" "test <test@example.com>" &&
- author_test gamma "<test@example.com>" "Unknown <test@example.com>" &&
- author_test delta "name<test@example.com>" "name <test@example.com>" &&
- author_test epsilon "name <test@example.com" "name <test@example.com>" &&
- author_test zeta " test " "test <unknown>" &&
- author_test eta "test < test@example.com >" "test <test@example.com>" &&
- author_test theta "test >test@example.com>" "test <test@example.com>" &&
- author_test iota "test < test <at> example <dot> com>" "test <unknown>" &&
- author_test kappa "test@example.com" "Unknown <test@example.com>"
+ author_test beta "beta" "beta <unknown>" &&
+ author_test gamma "gamma <test@example.com> (comment)" "gamma <test@example.com>" &&
+ author_test delta "<delta@example.com>" "Unknown <delta@example.com>" &&
+ author_test epsilon "epsilon<test@example.com>" "epsilon <test@example.com>" &&
+ author_test zeta "zeta <test@example.com" "zeta <test@example.com>" &&
+ author_test eta " eta " "eta <unknown>" &&
+ author_test theta "theta < test@example.com >" "theta <test@example.com>" &&
+ author_test iota "iota >test@example.com>" "iota <test@example.com>" &&
+ author_test kappa "kappa < test <at> example <dot> com>" "kappa <unknown>" &&
+ author_test lambda "lambda@example.com" "Unknown <lambda@example.com>" &&
+ author_test mu "mu.mu@example.com" "Unknown <mu.mu@example.com>"
) &&
git clone "hg::hgrepo" gitrepo &&
@@ -335,6 +337,17 @@ test_expect_success 'remote cloning' '
check gitrepo HEAD zero
'
+test_expect_success 'moving remote clone' '
+ test_when_finished "rm -rf gitrepo*" &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ mv gitrepo gitrepo2 &&
+ cd gitrepo2 &&
+ git fetch
+ )
+'
+
test_expect_success 'remote update bookmark' '
test_when_finished "rm -rf gitrepo*" &&
@@ -442,6 +455,74 @@ test_expect_success 'remote new bookmark multiple branch head' '
# cleanup previous stuff
rm -rf hgrepo
+test_expect_success 'fetch special filenames' '
+ test_when_finished "rm -rf hgrepo gitrepo && LC_ALL=C" &&
+
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+
+ echo test >> "æ rø" &&
+ hg add "æ rø" &&
+ echo test >> "ø~?" &&
+ hg add "ø~?" &&
+ hg commit -m add-utf-8 &&
+ echo test >> "æ rø" &&
+ hg commit -m test-utf-8 &&
+ hg rm "ø~?" &&
+ hg mv "æ rø" "ø~?" &&
+ hg commit -m hg-mv-utf-8
+ ) &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+ git -c core.quotepath=false ls-files > ../actual
+ ) &&
+ echo "ø~?" > expected &&
+ test_cmp expected actual
+'
+
+test_expect_success 'push special filenames' '
+ test_when_finished "rm -rf hgrepo gitrepo && LC_ALL=C" &&
+
+ mkdir -p tmp && cd tmp &&
+
+ LC_ALL=en_US.UTF-8
+ export LC_ALL
+
+ (
+ hg init hgrepo &&
+ cd hgrepo &&
+
+ echo one >> content &&
+ hg add content &&
+ hg commit -m one
+ ) &&
+
+ (
+ git clone "hg::hgrepo" gitrepo &&
+ cd gitrepo &&
+
+ echo test >> "æ rø" &&
+ git add "æ rø" &&
+ git commit -m utf-8 &&
+
+ git push
+ ) &&
+
+ (cd hgrepo &&
+ hg update &&
+ hg manifest > ../actual
+ ) &&
+
+ printf "content\næ rø\n" > expected &&
+ test_cmp expected actual
+'
+
setup_big_push () {
(
hg init hgrepo &&
@@ -599,7 +680,7 @@ test_expect_success 'remote big push fetch first' '
)
'
-test_expect_failure 'remote big push force' '
+test_expect_success 'remote big push force' '
test_when_finished "rm -rf hgrepo gitrepo*" &&
setup_big_push
@@ -629,7 +710,7 @@ test_expect_failure 'remote big push force' '
check_bookmark hgrepo new_bmark six
'
-test_expect_failure 'remote big push dry-run' '
+test_expect_success 'remote big push dry-run' '
test_when_finished "rm -rf hgrepo gitrepo*" &&
setup_big_push
@@ -691,4 +772,77 @@ test_expect_success 'remote double failed push' '
)
'
+test_expect_success 'clone remote with master null bookmark, then push to the bookmark' '
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
+
+ hg init hgrepo &&
+ (
+ cd hgrepo &&
+ echo a >a &&
+ hg add a &&
+ hg commit -m a &&
+ hg bookmark -r null master
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD a &&
+ (
+ cd gitrepo &&
+ git checkout --quiet -b master &&
+ echo b >b &&
+ git add b &&
+ git commit -m b &&
+ git push origin master
+ )
+'
+
+test_expect_success 'clone remote with default null bookmark, then push to the bookmark' '
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
+
+ hg init hgrepo &&
+ (
+ cd hgrepo &&
+ echo a >a &&
+ hg add a &&
+ hg commit -m a &&
+ hg bookmark -r null -f default
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD a &&
+ (
+ cd gitrepo &&
+ git checkout --quiet -b default &&
+ echo b >b &&
+ git add b &&
+ git commit -m b &&
+ git push origin default
+ )
+'
+
+test_expect_success 'clone remote with generic null bookmark, then push to the bookmark' '
+ test_when_finished "rm -rf gitrepo* hgrepo*" &&
+
+ hg init hgrepo &&
+ (
+ cd hgrepo &&
+ echo a >a &&
+ hg add a &&
+ hg commit -m a &&
+ hg bookmark -r null bmark
+ ) &&
+
+ git clone "hg::hgrepo" gitrepo &&
+ check gitrepo HEAD a &&
+ (
+ cd gitrepo &&
+ git checkout --quiet -b bmark &&
+ git remote -v &&
+ echo b >b &&
+ git add b &&
+ git commit -m b &&
+ git push origin bmark
+ )
+'
+
test_done
diff --git a/contrib/subtree/git-subtree.sh b/contrib/subtree/git-subtree.sh
index 7d7af03274..db925ca769 100755
--- a/contrib/subtree/git-subtree.sh
+++ b/contrib/subtree/git-subtree.sh
@@ -9,10 +9,10 @@ if [ $# -eq 0 ]; then
fi
OPTS_SPEC="\
git subtree add --prefix=<prefix> <commit>
-git subtree add --prefix=<prefix> <repository> <commit>
+git subtree add --prefix=<prefix> <repository> <ref>
git subtree merge --prefix=<prefix> <commit>
-git subtree pull --prefix=<prefix> <repository> <refspec...>
-git subtree push --prefix=<prefix> <repository> <refspec...>
+git subtree pull --prefix=<prefix> <repository> <ref>
+git subtree push --prefix=<prefix> <repository> <ref>
git subtree split --prefix=<prefix> <commit...>
--
h,help show the help
@@ -46,6 +46,7 @@ ignore_joins=
annotate=
squash=
message=
+prefix=
debug()
{
@@ -489,6 +490,12 @@ ensure_clean()
fi
}
+ensure_valid_ref_format()
+{
+ git check-ref-format "refs/heads/$1" ||
+ die "'$1' does not look like a ref"
+}
+
cmd_add()
{
if [ -e "$dir" ]; then
@@ -508,8 +515,7 @@ cmd_add()
# specified directory. Allowing a refspec might be
# misleading because we won't do anything with any other
# branches fetched via the refspec.
- git rev-parse -q --verify "$2^{commit}" >/dev/null ||
- die "'$2' does not refer to a commit"
+ ensure_valid_ref_format "$2"
"cmd_add_repository" "$@"
else
@@ -699,7 +705,11 @@ cmd_merge()
cmd_pull()
{
+ if [ $# -ne 2 ]; then
+ die "You must provide <repository> <ref>"
+ fi
ensure_clean
+ ensure_valid_ref_format "$2"
git fetch "$@" || exit $?
revs=FETCH_HEAD
set -- $revs
@@ -709,8 +719,9 @@ cmd_pull()
cmd_push()
{
if [ $# -ne 2 ]; then
- die "You must provide <repository> <refspec>"
+ die "You must provide <repository> <ref>"
fi
+ ensure_valid_ref_format "$2"
if [ -e "$dir" ]; then
repository=$1
refspec=$2
diff --git a/contrib/subtree/git-subtree.txt b/contrib/subtree/git-subtree.txt
index e0957eee55..02669b1534 100644
--- a/contrib/subtree/git-subtree.txt
+++ b/contrib/subtree/git-subtree.txt
@@ -9,10 +9,10 @@ git-subtree - Merge subtrees together and split repository into subtrees
SYNOPSIS
--------
[verse]
-'git subtree' add -P <prefix> <refspec>
-'git subtree' add -P <prefix> <repository> <refspec>
-'git subtree' pull -P <prefix> <repository> <refspec...>
-'git subtree' push -P <prefix> <repository> <refspec...>
+'git subtree' add -P <prefix> <commit>
+'git subtree' add -P <prefix> <repository> <ref>
+'git subtree' pull -P <prefix> <repository> <ref>
+'git subtree' push -P <prefix> <repository> <ref>
'git subtree' merge -P <prefix> <commit>
'git subtree' split -P <prefix> [OPTIONS] [<commit>]
@@ -68,7 +68,7 @@ COMMANDS
--------
add::
Create the <prefix> subtree by importing its contents
- from the given <refspec> or <repository> and remote <refspec>.
+ from the given <commit> or <repository> and remote <ref>.
A new commit is created automatically, joining the imported
project's history with your own. With '--squash', imports
only a single commit from the subproject, rather than its
@@ -90,13 +90,13 @@ merge::
pull::
Exactly like 'merge', but parallels 'git pull' in that
- it fetches the given commit from the specified remote
+ it fetches the given ref from the specified remote
repository.
push::
Does a 'split' (see below) using the <prefix> supplied
and then does a 'git push' to push the result to the
- repository and refspec. This can be used to push your
+ repository and ref. This can be used to push your
subtree to different branches of the remote repository.
split::
diff --git a/contrib/svn-fe/svn-fe.txt b/contrib/svn-fe/svn-fe.txt
index 1128ab2ce4..a3425f4770 100644
--- a/contrib/svn-fe/svn-fe.txt
+++ b/contrib/svn-fe/svn-fe.txt
@@ -40,8 +40,8 @@ manual page.
NOTES
-----
Subversion dumps do not record a separate author and committer for
-each revision, nor a separate display name and email address for
-each author. Like git-svn(1), 'svn-fe' will use the name
+each revision, nor do they record a separate display name and email
+address for each author. Like git-svn(1), 'svn-fe' will use the name
---------
user <user@UUID>