summaryrefslogtreecommitdiff
path: root/contrib
diff options
context:
space:
mode:
Diffstat (limited to 'contrib')
-rw-r--r--contrib/coccinelle/.gitignore1
-rw-r--r--contrib/coccinelle/README2
-rw-r--r--contrib/coccinelle/array.cocci26
-rw-r--r--contrib/coccinelle/free.cocci5
-rw-r--r--contrib/coccinelle/object_id.cocci110
-rw-r--r--contrib/coccinelle/qsort.cocci37
-rw-r--r--contrib/coccinelle/strbuf.cocci40
-rw-r--r--contrib/coccinelle/xstrdup_or_null.cocci7
-rw-r--r--contrib/completion/git-completion.bash169
-rw-r--r--contrib/completion/git-prompt.sh6
-rw-r--r--contrib/credential/gnome-keyring/Makefile5
-rw-r--r--contrib/credential/libsecret/Makefile25
-rw-r--r--contrib/credential/libsecret/git-credential-libsecret.c370
-rw-r--r--contrib/diff-highlight/Makefile5
-rwxr-xr-xcontrib/diff-highlight/diff-highlight19
-rw-r--r--contrib/diff-highlight/t/.gitignore2
-rw-r--r--contrib/diff-highlight/t/Makefile22
-rwxr-xr-xcontrib/diff-highlight/t/t9400-diff-highlight.sh296
-rwxr-xr-xcontrib/fast-import/import-tars.perl31
-rw-r--r--contrib/git-jump/README6
-rwxr-xr-xcontrib/git-jump/git-jump8
-rw-r--r--contrib/hooks/multimail/CHANGES67
-rw-r--r--contrib/hooks/multimail/CONTRIBUTING.rst9
-rw-r--r--contrib/hooks/multimail/README89
-rw-r--r--contrib/hooks/multimail/README.Git4
-rw-r--r--contrib/hooks/multimail/doc/troubleshooting.rst34
-rwxr-xr-xcontrib/hooks/multimail/git_multimail.py818
-rwxr-xr-xcontrib/long-running-filter/example.pl128
-rw-r--r--contrib/mw-to-git/.perlcriticrc2
-rwxr-xr-xcontrib/mw-to-git/git-remote-mediawiki.perl2
-rw-r--r--contrib/persistent-https/Makefile6
-rwxr-xr-xcontrib/subtree/git-subtree.sh678
-rwxr-xr-xcontrib/subtree/t/t7900-subtree.sh34
-rw-r--r--contrib/update-unicode/.gitignore3
-rw-r--r--contrib/update-unicode/README20
-rwxr-xr-xcontrib/update-unicode/update_unicode.sh33
36 files changed, 2505 insertions, 614 deletions
diff --git a/contrib/coccinelle/.gitignore b/contrib/coccinelle/.gitignore
new file mode 100644
index 0000000000..d3f29646dc
--- /dev/null
+++ b/contrib/coccinelle/.gitignore
@@ -0,0 +1 @@
+*.patch*
diff --git a/contrib/coccinelle/README b/contrib/coccinelle/README
new file mode 100644
index 0000000000..9c2f8879c2
--- /dev/null
+++ b/contrib/coccinelle/README
@@ -0,0 +1,2 @@
+This directory provides examples of Coccinelle (http://coccinelle.lip6.fr/)
+semantic patches that might be useful to developers.
diff --git a/contrib/coccinelle/array.cocci b/contrib/coccinelle/array.cocci
new file mode 100644
index 0000000000..2d7f25d99f
--- /dev/null
+++ b/contrib/coccinelle/array.cocci
@@ -0,0 +1,26 @@
+@@
+type T;
+T *dst;
+T *src;
+expression n;
+@@
+- memcpy(dst, src, n * sizeof(*dst));
++ COPY_ARRAY(dst, src, n);
+
+@@
+type T;
+T *dst;
+T *src;
+expression n;
+@@
+- memcpy(dst, src, n * sizeof(*src));
++ COPY_ARRAY(dst, src, n);
+
+@@
+type T;
+T *dst;
+T *src;
+expression n;
+@@
+- memcpy(dst, src, n * sizeof(T));
++ COPY_ARRAY(dst, src, n);
diff --git a/contrib/coccinelle/free.cocci b/contrib/coccinelle/free.cocci
new file mode 100644
index 0000000000..e28213161a
--- /dev/null
+++ b/contrib/coccinelle/free.cocci
@@ -0,0 +1,5 @@
+@@
+expression E;
+@@
+- if (E)
+ free(E);
diff --git a/contrib/coccinelle/object_id.cocci b/contrib/coccinelle/object_id.cocci
new file mode 100644
index 0000000000..09afdbf994
--- /dev/null
+++ b/contrib/coccinelle/object_id.cocci
@@ -0,0 +1,110 @@
+@@
+expression E1;
+@@
+- is_null_sha1(E1.hash)
++ is_null_oid(&E1)
+
+@@
+expression E1;
+@@
+- is_null_sha1(E1->hash)
++ is_null_oid(E1)
+
+@@
+expression E1;
+@@
+- sha1_to_hex(E1.hash)
++ oid_to_hex(&E1)
+
+@@
+identifier f != oid_to_hex;
+expression E1;
+@@
+ f(...) {...
+- sha1_to_hex(E1->hash)
++ oid_to_hex(E1)
+ ...}
+
+@@
+expression E1, E2;
+@@
+- sha1_to_hex_r(E1, E2.hash)
++ oid_to_hex_r(E1, &E2)
+
+@@
+identifier f != oid_to_hex_r;
+expression E1, E2;
+@@
+ f(...) {...
+- sha1_to_hex_r(E1, E2->hash)
++ oid_to_hex_r(E1, E2)
+ ...}
+
+@@
+expression E1;
+@@
+- hashclr(E1.hash)
++ oidclr(&E1)
+
+@@
+identifier f != oidclr;
+expression E1;
+@@
+ f(...) {...
+- hashclr(E1->hash)
++ oidclr(E1)
+ ...}
+
+@@
+expression E1, E2;
+@@
+- hashcmp(E1.hash, E2.hash)
++ oidcmp(&E1, &E2)
+
+@@
+identifier f != oidcmp;
+expression E1, E2;
+@@
+ f(...) {...
+- hashcmp(E1->hash, E2->hash)
++ oidcmp(E1, E2)
+ ...}
+
+@@
+expression E1, E2;
+@@
+- hashcmp(E1->hash, E2.hash)
++ oidcmp(E1, &E2)
+
+@@
+expression E1, E2;
+@@
+- hashcmp(E1.hash, E2->hash)
++ oidcmp(&E1, E2)
+
+@@
+expression E1, E2;
+@@
+- hashcpy(E1.hash, E2.hash)
++ oidcpy(&E1, &E2)
+
+@@
+identifier f != oidcpy;
+expression E1, E2;
+@@
+ f(...) {...
+- hashcpy(E1->hash, E2->hash)
++ oidcpy(E1, E2)
+ ...}
+
+@@
+expression E1, E2;
+@@
+- hashcpy(E1->hash, E2.hash)
++ oidcpy(E1, &E2)
+
+@@
+expression E1, E2;
+@@
+- hashcpy(E1.hash, E2->hash)
++ oidcpy(&E1, E2)
diff --git a/contrib/coccinelle/qsort.cocci b/contrib/coccinelle/qsort.cocci
new file mode 100644
index 0000000000..22b93a9966
--- /dev/null
+++ b/contrib/coccinelle/qsort.cocci
@@ -0,0 +1,37 @@
+@@
+expression base, nmemb, compar;
+@@
+- qsort(base, nmemb, sizeof(*base), compar);
++ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- qsort(base, nmemb, sizeof(base[0]), compar);
++ QSORT(base, nmemb, compar);
+
+@@
+type T;
+T *base;
+expression nmemb, compar;
+@@
+- qsort(base, nmemb, sizeof(T), compar);
++ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- if (nmemb)
+ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- if (nmemb > 0)
+ QSORT(base, nmemb, compar);
+
+@@
+expression base, nmemb, compar;
+@@
+- if (nmemb > 1)
+ QSORT(base, nmemb, compar);
diff --git a/contrib/coccinelle/strbuf.cocci b/contrib/coccinelle/strbuf.cocci
new file mode 100644
index 0000000000..63995f22ff
--- /dev/null
+++ b/contrib/coccinelle/strbuf.cocci
@@ -0,0 +1,40 @@
+@ strbuf_addf_with_format_only @
+expression E;
+constant fmt;
+@@
+ strbuf_addf(E,
+(
+ fmt
+|
+ _(fmt)
+)
+ );
+
+@ script:python @
+fmt << strbuf_addf_with_format_only.fmt;
+@@
+cocci.include_match("%" not in fmt)
+
+@ extends strbuf_addf_with_format_only @
+@@
+- strbuf_addf
++ strbuf_addstr
+ (E,
+(
+ fmt
+|
+ _(fmt)
+)
+ );
+
+@@
+expression E1, E2;
+@@
+- strbuf_addf(E1, "%s", E2);
++ strbuf_addstr(E1, E2);
+
+@@
+expression E1, E2, E3;
+@@
+- strbuf_addstr(E1, find_unique_abbrev(E2, E3));
++ strbuf_add_unique_abbrev(E1, E2, E3);
diff --git a/contrib/coccinelle/xstrdup_or_null.cocci b/contrib/coccinelle/xstrdup_or_null.cocci
new file mode 100644
index 0000000000..3fceef132b
--- /dev/null
+++ b/contrib/coccinelle/xstrdup_or_null.cocci
@@ -0,0 +1,7 @@
+@@
+expression E;
+expression V;
+@@
+- if (E)
+- V = xstrdup(E);
++ V = xstrdup_or_null(E);
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index e3918c87e3..78fe5b7f5c 100644
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -338,7 +338,7 @@ __git_tags ()
__git_refs ()
{
local i hash dir="$(__gitdir "${1-}")" track="${2-}"
- local format refs
+ local format refs pfx
if [ -d "$dir" ]; then
case "$cur" in
refs|refs/*)
@@ -347,14 +347,15 @@ __git_refs ()
track=""
;;
*)
+ [[ "$cur" == ^* ]] && pfx="^"
for i in HEAD FETCH_HEAD ORIG_HEAD MERGE_HEAD; do
- if [ -e "$dir/$i" ]; then echo $i; fi
+ if [ -e "$dir/$i" ]; then echo $pfx$i; fi
done
format="refname:short"
refs="refs/tags refs/heads refs/remotes"
;;
esac
- git --git-dir="$dir" for-each-ref --format="%($format)" \
+ git --git-dir="$dir" for-each-ref --format="$pfx%($format)" \
$refs
if [ -n "$track" ]; then
# employ the heuristic used by git checkout
@@ -803,6 +804,50 @@ __git_find_on_cmdline ()
done
}
+# Echo the value of an option set on the command line or config
+#
+# $1: short option name
+# $2: long option name including =
+# $3: list of possible values
+# $4: config string (optional)
+#
+# example:
+# result="$(__git_get_option_value "-d" "--do-something=" \
+# "yes no" "core.doSomething")"
+#
+# result is then either empty (no option set) or "yes" or "no"
+#
+# __git_get_option_value requires 3 arguments
+__git_get_option_value ()
+{
+ local c short_opt long_opt val
+ local result= values config_key word
+
+ short_opt="$1"
+ long_opt="$2"
+ values="$3"
+ config_key="$4"
+
+ ((c = $cword - 1))
+ while [ $c -ge 0 ]; do
+ word="${words[c]}"
+ for val in $values; do
+ if [ "$short_opt$val" = "$word" ] ||
+ [ "$long_opt$val" = "$word" ]; then
+ result="$val"
+ break 2
+ fi
+ done
+ ((c--))
+ done
+
+ if [ -n "$config_key" ] && [ -z "$result" ]; then
+ result="$(git --git-dir="$(__gitdir)" config "$config_key")"
+ fi
+
+ echo "$result"
+}
+
__git_has_doubledash ()
{
local c=1
@@ -964,8 +1009,8 @@ _git_branch ()
while [ $c -lt $cword ]; do
i="${words[c]}"
case "$i" in
- -d|-m) only_local_ref="y" ;;
- -r) has_r="y" ;;
+ -d|--delete|-m|--move) only_local_ref="y" ;;
+ -r|--remotes) has_r="y" ;;
esac
((c++))
done
@@ -979,7 +1024,7 @@ _git_branch ()
--color --no-color --verbose --abbrev= --no-abbrev
--track --no-track --contains --merged --no-merged
--set-upstream-to= --edit-description --list
- --unset-upstream
+ --unset-upstream --delete --move --remotes
"
;;
*)
@@ -1092,12 +1137,15 @@ _git_clone ()
--depth
--single-branch
--branch
+ --recurse-submodules
"
return
;;
esac
}
+__git_untracked_file_modes="all no normal"
+
_git_commit ()
{
case "$prev" in
@@ -1119,7 +1167,7 @@ _git_commit ()
return
;;
--untracked-files=*)
- __gitcomp "all no normal" "" "${cur##--untracked-files=}"
+ __gitcomp "$__git_untracked_file_modes" "" "${cur##--untracked-files=}"
return
;;
--*)
@@ -1158,6 +1206,8 @@ _git_describe ()
__git_diff_algorithms="myers minimal patience histogram"
+__git_diff_submodule_formats="log short"
+
__git_diff_common_options="--stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
--no-color --color-words --no-renames --check
@@ -1173,6 +1223,7 @@ __git_diff_common_options="--stat --numstat --shortstat --summary
--dirstat --dirstat= --dirstat-by-file
--dirstat-by-file= --cumulative
--diff-algorithm=
+ --submodule --submodule=
"
_git_diff ()
@@ -1184,6 +1235,10 @@ _git_diff ()
__gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
return
;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
+ return
+ ;;
--*)
__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
--base --ours --theirs --no-index
@@ -1339,15 +1394,15 @@ _git_help ()
{
case "$cur" in
--*)
- __gitcomp "--all --info --man --web"
+ __gitcomp "--all --guides --info --man --web"
return
;;
esac
__git_compute_all_commands
__gitcomp "$__git_all_commands $(__git_aliases)
attributes cli core-tutorial cvs-migration
- diffcore gitk glossary hooks ignore modules
- namespaces repository-layout tutorial tutorial-2
+ diffcore everyday gitk glossary hooks ignore modules
+ namespaces repository-layout revisions tutorial tutorial-2
workflows
"
}
@@ -1447,6 +1502,14 @@ _git_log ()
__gitcomp "full short no" "" "${cur##--decorate=}"
return
;;
+ --diff-algorithm=*)
+ __gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
+ return
+ ;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
+ return
+ ;;
--*)
__gitcomp "
$__git_log_common_options
@@ -1458,6 +1521,7 @@ _git_log ()
--relative-date --date=
--pretty= --format= --oneline
--show-signature
+ --cherry-mark
--cherry-pick
--graph
--decorate --decorate=
@@ -1670,10 +1734,10 @@ _git_rebase ()
{
local dir="$(__gitdir)"
if [ -f "$dir"/rebase-merge/interactive ]; then
- __gitcomp "--continue --skip --abort --edit-todo"
+ __gitcomp "--continue --skip --abort --quit --edit-todo"
return
elif [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
- __gitcomp "--continue --skip --abort"
+ __gitcomp "--continue --skip --abort --quit"
return
fi
__git_complete_strategy && return
@@ -1779,6 +1843,56 @@ _git_stage ()
_git_add
}
+_git_status ()
+{
+ local complete_opt
+ local untracked_state
+
+ case "$cur" in
+ --ignore-submodules=*)
+ __gitcomp "none untracked dirty all" "" "${cur##--ignore-submodules=}"
+ return
+ ;;
+ --untracked-files=*)
+ __gitcomp "$__git_untracked_file_modes" "" "${cur##--untracked-files=}"
+ return
+ ;;
+ --column=*)
+ __gitcomp "
+ always never auto column row plain dense nodense
+ " "" "${cur##--column=}"
+ return
+ ;;
+ --*)
+ __gitcomp "
+ --short --branch --porcelain --long --verbose
+ --untracked-files= --ignore-submodules= --ignored
+ --column= --no-column
+ "
+ return
+ ;;
+ esac
+
+ untracked_state="$(__git_get_option_value "-u" "--untracked-files=" \
+ "$__git_untracked_file_modes" "status.showUntrackedFiles")"
+
+ case "$untracked_state" in
+ no)
+ # --ignored option does not matter
+ complete_opt=
+ ;;
+ all|normal|*)
+ complete_opt="--cached --directory --no-empty-directory --others"
+
+ if [ -n "$(__git_find_on_cmdline "--ignored")" ]; then
+ complete_opt="$complete_opt --ignored --exclude=*"
+ fi
+ ;;
+ esac
+
+ __git_complete_index_file "$complete_opt"
+}
+
__git_config_get_set_variables ()
{
local prevword word config_file= c=$cword
@@ -2084,6 +2198,7 @@ _git_config ()
format.attach
format.cc
format.coverLetter
+ format.from
format.headers
format.numbered
format.pretty
@@ -2358,6 +2473,10 @@ _git_show ()
__gitcomp "$__git_diff_algorithms" "" "${cur##--diff-algorithm=}"
return
;;
+ --submodule=*)
+ __gitcomp "$__git_diff_submodule_formats" "" "${cur##--submodule=}"
+ return
+ ;;
--*)
__gitcomp "--pretty= --format= --abbrev-commit --oneline
--show-signature
@@ -2594,6 +2713,32 @@ _git_whatchanged ()
_git_log
}
+_git_worktree ()
+{
+ local subcommands="add list lock prune unlock"
+ local subcommand="$(__git_find_on_cmdline "$subcommands")"
+ if [ -z "$subcommand" ]; then
+ __gitcomp "$subcommands"
+ else
+ case "$subcommand,$cur" in
+ add,--*)
+ __gitcomp "--detach"
+ ;;
+ list,--*)
+ __gitcomp "--porcelain"
+ ;;
+ lock,--*)
+ __gitcomp "--reason"
+ ;;
+ prune,--*)
+ __gitcomp "--dry-run --expire --verbose"
+ ;;
+ *)
+ ;;
+ esac
+ fi
+}
+
__git_main ()
{
local i c=1 command __git_dir
diff --git a/contrib/completion/git-prompt.sh b/contrib/completion/git-prompt.sh
index 64219e631a..97eacd7832 100644
--- a/contrib/completion/git-prompt.sh
+++ b/contrib/completion/git-prompt.sh
@@ -355,8 +355,8 @@ __git_ps1 ()
# incorrect.)
#
local ps1_expanded=yes
- [ -z "$ZSH_VERSION" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
- [ -z "$BASH_VERSION" ] || shopt -q promptvars || ps1_expanded=no
+ [ -z "${ZSH_VERSION-}" ] || [[ -o PROMPT_SUBST ]] || ps1_expanded=no
+ [ -z "${BASH_VERSION-}" ] || shopt -q promptvars || ps1_expanded=no
local repo_info rev_parse_exit_code
repo_info="$(git rev-parse --git-dir --is-inside-git-dir \
@@ -368,7 +368,7 @@ __git_ps1 ()
return $exit
fi
- local short_sha
+ local short_sha=""
if [ "$rev_parse_exit_code" = "0" ]; then
short_sha="${repo_info##*$'\n'}"
repo_info="${repo_info%$'\n'*}"
diff --git a/contrib/credential/gnome-keyring/Makefile b/contrib/credential/gnome-keyring/Makefile
index c3c7c98aa1..22c19df94b 100644
--- a/contrib/credential/gnome-keyring/Makefile
+++ b/contrib/credential/gnome-keyring/Makefile
@@ -4,12 +4,13 @@ all:: $(MAIN)
CC = gcc
RM = rm -f
CFLAGS = -g -O2 -Wall
+PKG_CONFIG = pkg-config
-include ../../../config.mak.autogen
-include ../../../config.mak
-INCS:=$(shell pkg-config --cflags gnome-keyring-1 glib-2.0)
-LIBS:=$(shell pkg-config --libs gnome-keyring-1 glib-2.0)
+INCS:=$(shell $(PKG_CONFIG) --cflags gnome-keyring-1 glib-2.0)
+LIBS:=$(shell $(PKG_CONFIG) --libs gnome-keyring-1 glib-2.0)
SRCS:=$(MAIN).c
OBJS:=$(SRCS:.c=.o)
diff --git a/contrib/credential/libsecret/Makefile b/contrib/credential/libsecret/Makefile
new file mode 100644
index 0000000000..3e67552cc5
--- /dev/null
+++ b/contrib/credential/libsecret/Makefile
@@ -0,0 +1,25 @@
+MAIN:=git-credential-libsecret
+all:: $(MAIN)
+
+CC = gcc
+RM = rm -f
+CFLAGS = -g -O2 -Wall
+PKG_CONFIG = pkg-config
+
+-include ../../../config.mak.autogen
+-include ../../../config.mak
+
+INCS:=$(shell $(PKG_CONFIG) --cflags libsecret-1 glib-2.0)
+LIBS:=$(shell $(PKG_CONFIG) --libs libsecret-1 glib-2.0)
+
+SRCS:=$(MAIN).c
+OBJS:=$(SRCS:.c=.o)
+
+%.o: %.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(INCS) -o $@ -c $<
+
+$(MAIN): $(OBJS)
+ $(CC) -o $@ $(LDFLAGS) $^ $(LIBS)
+
+clean:
+ @$(RM) $(MAIN) $(OBJS)
diff --git a/contrib/credential/libsecret/git-credential-libsecret.c b/contrib/credential/libsecret/git-credential-libsecret.c
new file mode 100644
index 0000000000..4c56979d8a
--- /dev/null
+++ b/contrib/credential/libsecret/git-credential-libsecret.c
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2011 John Szakmeister <john@szakmeister.net>
+ * 2012 Philipp A. Hartmann <pah@qo.cx>
+ * 2016 Mantas Mikulėnas <grawity@gmail.com>
+ *
+ * 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 of the License, 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
+ */
+
+/*
+ * Credits:
+ * - GNOME Keyring API handling originally written by John Szakmeister
+ * - ported to credential helper API by Philipp A. Hartmann
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+#include <libsecret/secret.h>
+
+/*
+ * This credential struct and API is simplified from git's credential.{h,c}
+ */
+struct credential {
+ char *protocol;
+ char *host;
+ unsigned short port;
+ char *path;
+ char *username;
+ char *password;
+};
+
+#define CREDENTIAL_INIT { NULL, NULL, 0, NULL, NULL, NULL }
+
+typedef int (*credential_op_cb)(struct credential *);
+
+struct credential_operation {
+ char *name;
+ credential_op_cb op;
+};
+
+#define CREDENTIAL_OP_END { NULL, NULL }
+
+/* ----------------- Secret Service functions ----------------- */
+
+static char *make_label(struct credential *c)
+{
+ if (c->port)
+ return g_strdup_printf("Git: %s://%s:%hu/%s",
+ c->protocol, c->host, c->port, c->path ? c->path : "");
+ else
+ return g_strdup_printf("Git: %s://%s/%s",
+ c->protocol, c->host, c->path ? c->path : "");
+}
+
+static GHashTable *make_attr_list(struct credential *c)
+{
+ GHashTable *al = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_free);
+
+ if (c->username)
+ g_hash_table_insert(al, "user", g_strdup(c->username));
+ if (c->protocol)
+ g_hash_table_insert(al, "protocol", g_strdup(c->protocol));
+ if (c->host)
+ g_hash_table_insert(al, "server", g_strdup(c->host));
+ if (c->port)
+ g_hash_table_insert(al, "port", g_strdup_printf("%hu", c->port));
+ if (c->path)
+ g_hash_table_insert(al, "object", g_strdup(c->path));
+
+ return al;
+}
+
+static int keyring_get(struct credential *c)
+{
+ SecretService *service = NULL;
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+ GList *items = NULL;
+
+ if (!c->protocol || !(c->host || c->path))
+ return EXIT_FAILURE;
+
+ service = secret_service_get_sync(0, NULL, &error);
+ if (error != NULL) {
+ g_critical("could not connect to Secret Service: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ attributes = make_attr_list(c);
+ items = secret_service_search_sync(service,
+ SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ SECRET_SEARCH_LOAD_SECRETS,
+ NULL,
+ &error);
+ g_hash_table_unref(attributes);
+ if (error != NULL) {
+ g_critical("lookup failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ if (items != NULL) {
+ SecretItem *item;
+ SecretValue *secret;
+ const char *s;
+
+ item = items->data;
+ secret = secret_item_get_secret(item);
+ attributes = secret_item_get_attributes(item);
+
+ s = g_hash_table_lookup(attributes, "user");
+ if (s) {
+ g_free(c->username);
+ c->username = g_strdup(s);
+ }
+
+ s = secret_value_get_text(secret);
+ if (s) {
+ g_free(c->password);
+ c->password = g_strdup(s);
+ }
+
+ g_hash_table_unref(attributes);
+ secret_value_unref(secret);
+ g_list_free_full(items, g_object_unref);
+ }
+
+ return EXIT_SUCCESS;
+}
+
+
+static int keyring_store(struct credential *c)
+{
+ char *label = NULL;
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+
+ /*
+ * Sanity check that what we are storing is actually sensible.
+ * In particular, we can't make a URL without a protocol field.
+ * Without either a host or pathname (depending on the scheme),
+ * we have no primary key. And without a username and password,
+ * we are not actually storing a credential.
+ */
+ if (!c->protocol || !(c->host || c->path) ||
+ !c->username || !c->password)
+ return EXIT_FAILURE;
+
+ label = make_label(c);
+ attributes = make_attr_list(c);
+ secret_password_storev_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ NULL,
+ label,
+ c->password,
+ NULL,
+ &error);
+ g_free(label);
+ g_hash_table_unref(attributes);
+
+ if (error != NULL) {
+ g_critical("store failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+static int keyring_erase(struct credential *c)
+{
+ GHashTable *attributes = NULL;
+ GError *error = NULL;
+
+ /*
+ * Sanity check that we actually have something to match
+ * against. The input we get is a restrictive pattern,
+ * so technically a blank credential means "erase everything".
+ * But it is too easy to accidentally send this, since it is equivalent
+ * to empty input. So explicitly disallow it, and require that the
+ * pattern have some actual content to match.
+ */
+ if (!c->protocol && !c->host && !c->path && !c->username)
+ return EXIT_FAILURE;
+
+ attributes = make_attr_list(c);
+ secret_password_clearv_sync(SECRET_SCHEMA_COMPAT_NETWORK,
+ attributes,
+ NULL,
+ &error);
+ g_hash_table_unref(attributes);
+
+ if (error != NULL) {
+ g_critical("erase failed: %s", error->message);
+ g_error_free(error);
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
+
+/*
+ * Table with helper operation callbacks, used by generic
+ * credential helper main function.
+ */
+static struct credential_operation const credential_helper_ops[] = {
+ { "get", keyring_get },
+ { "store", keyring_store },
+ { "erase", keyring_erase },
+ CREDENTIAL_OP_END
+};
+
+/* ------------------ credential functions ------------------ */
+
+static void credential_init(struct credential *c)
+{
+ memset(c, 0