summaryrefslogtreecommitdiff
path: root/builtin
diff options
context:
space:
mode:
Diffstat (limited to 'builtin')
-rw-r--r--builtin/add.c6
-rw-r--r--builtin/apply.c63
-rw-r--r--builtin/blame.c28
-rw-r--r--builtin/branch.c3
-rw-r--r--builtin/checkout-index.c3
-rw-r--r--builtin/clone.c2
-rw-r--r--builtin/commit.c87
-rw-r--r--builtin/diff.c2
-rw-r--r--builtin/gc.c3
-rw-r--r--builtin/grep.c4
-rw-r--r--builtin/log.c5
-rw-r--r--builtin/ls-files.c3
-rw-r--r--builtin/mailinfo.c2
-rw-r--r--builtin/merge.c48
-rw-r--r--builtin/notes.c268
-rw-r--r--builtin/pack-objects.c22
-rw-r--r--builtin/remote-ext.c246
-rw-r--r--builtin/remote-fd.c79
-rw-r--r--builtin/revert.c18
-rw-r--r--builtin/tag.c16
-rw-r--r--builtin/update-index.c3
-rw-r--r--builtin/verify-tag.c10
22 files changed, 781 insertions, 140 deletions
diff --git a/builtin/add.c b/builtin/add.c
index 71f9b04fef..12b964e642 100644
--- a/builtin/add.c
+++ b/builtin/add.c
@@ -331,7 +331,8 @@ static struct option builtin_add_options[] = {
static int add_config(const char *var, const char *value, void *cb)
{
- if (!strcasecmp(var, "add.ignore-errors")) {
+ if (!strcasecmp(var, "add.ignoreerrors") ||
+ !strcasecmp(var, "add.ignore-errors")) {
ignore_add_errors = git_config_bool(var, value);
return 0;
}
@@ -446,7 +447,8 @@ int cmd_add(int argc, const char **argv, const char *prefix)
if (!seen[i] && pathspec[i][0]
&& !file_exists(pathspec[i])) {
if (ignore_missing) {
- if (excluded(&dir, pathspec[i], DT_UNKNOWN))
+ int dtype = DT_UNKNOWN;
+ if (excluded(&dir, pathspec[i], &dtype))
dir_add_ignored(&dir, pathspec[i], strlen(pathspec[i]));
} else
die("pathspec '%s' did not match any files",
diff --git a/builtin/apply.c b/builtin/apply.c
index 96246e960f..14951daedf 100644
--- a/builtin/apply.c
+++ b/builtin/apply.c
@@ -449,7 +449,7 @@ static char *find_name_gnu(const char *line, char *def, int p_value)
return squash_slash(strbuf_detach(&name, NULL));
}
-static size_t tz_len(const char *line, size_t len)
+static size_t sane_tz_len(const char *line, size_t len)
{
const char *tz, *p;
@@ -467,6 +467,24 @@ static size_t tz_len(const char *line, size_t len)
return line + len - tz;
}
+static size_t tz_with_colon_len(const char *line, size_t len)
+{
+ const char *tz, *p;
+
+ if (len < strlen(" +08:00") || line[len - strlen(":00")] != ':')
+ return 0;
+ tz = line + len - strlen(" +08:00");
+
+ if (tz[0] != ' ' || (tz[1] != '+' && tz[1] != '-'))
+ return 0;
+ p = tz + 2;
+ if (!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
+ !isdigit(*p++) || !isdigit(*p++))
+ return 0;
+
+ return line + len - tz;
+}
+
static size_t date_len(const char *line, size_t len)
{
const char *date, *p;
@@ -561,7 +579,9 @@ static size_t diff_timestamp_len(const char *line, size_t len)
if (!isdigit(end[-1]))
return 0;
- n = tz_len(line, end - line);
+ n = sane_tz_len(line, end - line);
+ if (!n)
+ n = tz_with_colon_len(line, end - line);
end -= n;
n = short_time_len(line, end - line);
@@ -733,8 +753,8 @@ static int has_epoch_timestamp(const char *nameline)
" "
"[0-2][0-9]:[0-5][0-9]:00(\\.0+)?"
" "
- "([-+][0-2][0-9][0-5][0-9])\n";
- const char *timestamp = NULL, *cp;
+ "([-+][0-2][0-9]:?[0-5][0-9])\n";
+ const char *timestamp = NULL, *cp, *colon;
static regex_t *stamp;
regmatch_t m[10];
int zoneoffset;
@@ -764,8 +784,11 @@ static int has_epoch_timestamp(const char *nameline)
return 0;
}
- zoneoffset = strtol(timestamp + m[3].rm_so + 1, NULL, 10);
- zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
+ zoneoffset = strtol(timestamp + m[3].rm_so + 1, (char **) &colon, 10);
+ if (*colon == ':')
+ zoneoffset = zoneoffset * 60 + strtol(colon + 1, NULL, 10);
+ else
+ zoneoffset = (zoneoffset / 100) * 60 + (zoneoffset % 100);
if (timestamp[m[3].rm_so] == '-')
zoneoffset = -zoneoffset;
@@ -919,28 +942,28 @@ static int gitdiff_newfile(const char *line, struct patch *patch)
static int gitdiff_copysrc(const char *line, struct patch *patch)
{
patch->is_copy = 1;
- patch->old_name = find_name(line, NULL, 0, 0);
+ patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_copydst(const char *line, struct patch *patch)
{
patch->is_copy = 1;
- patch->new_name = find_name(line, NULL, 0, 0);
+ patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_renamesrc(const char *line, struct patch *patch)
{
patch->is_rename = 1;
- patch->old_name = find_name(line, NULL, 0, 0);
+ patch->old_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
static int gitdiff_renamedst(const char *line, struct patch *patch)
{
patch->is_rename = 1;
- patch->new_name = find_name(line, NULL, 0, 0);
+ patch->new_name = find_name(line, NULL, p_value ? p_value - 1 : 0, 0);
return 0;
}
@@ -1025,7 +1048,7 @@ static char *git_header_name(char *line, int llen)
{
const char *name;
const char *second = NULL;
- size_t len;
+ size_t len, line_len;
line += strlen("diff --git ");
llen -= strlen("diff --git ");
@@ -1125,6 +1148,10 @@ static char *git_header_name(char *line, int llen)
* Accept a name only if it shows up twice, exactly the same
* form.
*/
+ second = strchr(name, '\n');
+ if (!second)
+ return NULL;
+ line_len = second - name;
for (len = 0 ; ; len++) {
switch (name[len]) {
default:
@@ -1132,15 +1159,11 @@ static char *git_header_name(char *line, int llen)
case '\n':
return NULL;
case '\t': case ' ':
- second = name+len;
- for (;;) {
- char c = *second++;
- if (c == '\n')
- return NULL;
- if (c == '/')
- break;
- }
- if (second[len] == '\n' && !memcmp(name, second, len)) {
+ second = stop_at_slash(name + len, line_len - len);
+ if (!second)
+ return NULL;
+ second++;
+ if (second[len] == '\n' && !strncmp(name, second, len)) {
return xmemdupz(name, len);
}
}
diff --git a/builtin/blame.c b/builtin/blame.c
index f5fccc1f67..cb25ec9ce8 100644
--- a/builtin/blame.c
+++ b/builtin/blame.c
@@ -1617,6 +1617,7 @@ static const char *format_time(unsigned long time, const char *tz_str,
#define OUTPUT_SHOW_NUMBER 040
#define OUTPUT_SHOW_SCORE 0100
#define OUTPUT_NO_AUTHOR 0200
+#define OUTPUT_SHOW_EMAIL 0400
static void emit_porcelain(struct scoreboard *sb, struct blame_entry *ent)
{
@@ -1682,12 +1683,17 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
}
printf("%.*s", length, hex);
- if (opt & OUTPUT_ANNOTATE_COMPAT)
- printf("\t(%10s\t%10s\t%d)", ci.author,
+ if (opt & OUTPUT_ANNOTATE_COMPAT) {
+ const char *name;
+ if (opt & OUTPUT_SHOW_EMAIL)
+ name = ci.author_mail;
+ else
+ name = ci.author;
+ printf("\t(%10s\t%10s\t%d)", name,
format_time(ci.author_time, ci.author_tz,
show_raw_time),
ent->lno + 1 + cnt);
- else {
+ } else {
if (opt & OUTPUT_SHOW_SCORE)
printf(" %*d %02d",
max_score_digits, ent->score,
@@ -1700,9 +1706,15 @@ static void emit_other(struct scoreboard *sb, struct blame_entry *ent, int opt)
ent->s_lno + 1 + cnt);
if (!(opt & OUTPUT_NO_AUTHOR)) {
- int pad = longest_author - utf8_strwidth(ci.author);
+ const char *name;
+ int pad;
+ if (opt & OUTPUT_SHOW_EMAIL)
+ name = ci.author_mail;
+ else
+ name = ci.author;
+ pad = longest_author - utf8_strwidth(name);
printf(" (%s%*s %10s",
- ci.author, pad, "",
+ name, pad, "",
format_time(ci.author_time,
ci.author_tz,
show_raw_time));
@@ -1840,7 +1852,10 @@ static void find_alignment(struct scoreboard *sb, int *option)
if (!(suspect->commit->object.flags & METAINFO_SHOWN)) {
suspect->commit->object.flags |= METAINFO_SHOWN;
get_commit_info(suspect->commit, &ci, 1);
- num = utf8_strwidth(ci.author);
+ if (*option & OUTPUT_SHOW_EMAIL)
+ num = utf8_strwidth(ci.author_mail);
+ else
+ num = utf8_strwidth(ci.author);
if (longest_author < num)
longest_author = num;
}
@@ -2289,6 +2304,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)
OPT_BIT('t', NULL, &output_option, "Show raw timestamp (Default: off)", OUTPUT_RAW_TIMESTAMP),
OPT_BIT('l', NULL, &output_option, "Show long commit SHA1 (Default: off)", OUTPUT_LONG_OBJECT_NAME),
OPT_BIT('s', NULL, &output_option, "Suppress author name and timestamp (Default: off)", OUTPUT_NO_AUTHOR),
+ OPT_BIT('e', "show-email", &output_option, "Show author email instead of name (Default: off)", OUTPUT_SHOW_EMAIL),
OPT_BIT('w', NULL, &xdl_opts, "Ignore whitespace differences", XDF_IGNORE_WHITESPACE),
OPT_STRING('S', NULL, &revs_file, "file", "Use revisions from <file> instead of calling git-rev-list"),
OPT_STRING(0, "contents", &contents_from, "file", "Use <file>'s contents as the final image"),
diff --git a/builtin/branch.c b/builtin/branch.c
index 807355a198..0cad20bb5a 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c
@@ -668,6 +668,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
OPT_END(),
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_branch_usage, options);
+
git_config(git_branch_config, NULL);
if (branch_use_color == -1)
diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c
index 1ee3044307..f1fec24745 100644
--- a/builtin/checkout-index.c
+++ b/builtin/checkout-index.c
@@ -241,6 +241,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_checkout_index_usage,
+ builtin_checkout_index_options);
git_config(git_default_config, NULL);
state.base_dir = "";
prefix_length = prefix ? strlen(prefix) : 0;
diff --git a/builtin/clone.c b/builtin/clone.c
index 19ed64041d..61e0989b5a 100644
--- a/builtin/clone.c
+++ b/builtin/clone.c
@@ -66,6 +66,8 @@ static struct option builtin_clone_options[] = {
"setup as shared repository"),
OPT_BOOLEAN(0, "recursive", &option_recursive,
"initialize submodules in the clone"),
+ OPT_BOOLEAN(0, "recurse_submodules", &option_recursive,
+ "initialize submodules in the clone"),
OPT_STRING(0, "template", &option_template, "path",
"path the template repository"),
OPT_STRING(0, "reference", &option_reference, "repo",
diff --git a/builtin/commit.c b/builtin/commit.c
index 4fd1a1692f..c045c9ef8c 100644
--- a/builtin/commit.c
+++ b/builtin/commit.c
@@ -69,6 +69,7 @@ static enum {
static const char *logfile, *force_author;
static const char *template_file;
static char *edit_message, *use_message;
+static char *fixup_message, *squash_message;
static char *author_name, *author_email, *author_date;
static int all, edit_flag, also, interactive, only, amend, signoff;
static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
@@ -124,6 +125,8 @@ static struct option builtin_commit_options[] = {
OPT_CALLBACK('m', "message", &message, "MESSAGE", "specify commit message", opt_parse_m),
OPT_STRING('c', "reedit-message", &edit_message, "COMMIT", "reuse and edit message from specified commit"),
OPT_STRING('C', "reuse-message", &use_message, "COMMIT", "reuse message from specified commit"),
+ OPT_STRING(0, "fixup", &fixup_message, "COMMIT", "use autosquash formatted message to fixup specified commit"),
+ OPT_STRING(0, "squash", &squash_message, "COMMIT", "use autosquash formatted message to squash specified commit"),
OPT_BOOLEAN(0, "reset-author", &renew_authorship, "the commit is authored by me now (used with -C-c/--amend)"),
OPT_BOOLEAN('s', "signoff", &signoff, "add Signed-off-by:"),
OPT_FILENAME('t', "template", &template_file, "use specified template file"),
@@ -565,6 +568,25 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
+ if (squash_message) {
+ /*
+ * Insert the proper subject line before other commit
+ * message options add their content.
+ */
+ if (use_message && !strcmp(use_message, squash_message))
+ strbuf_addstr(&sb, "squash! ");
+ else {
+ struct pretty_print_context ctx = {0};
+ struct commit *c;
+ c = lookup_commit_reference_by_name(squash_message);
+ if (!c)
+ die("could not lookup commit %s", squash_message);
+ ctx.output_encoding = get_commit_output_encoding();
+ format_commit_message(c, "squash! %s\n\n", &sb,
+ &ctx);
+ }
+ }
+
if (message.len) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
@@ -586,6 +608,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_add(&sb, buffer + 2, strlen(buffer + 2));
hook_arg1 = "commit";
hook_arg2 = use_message;
+ } else if (fixup_message) {
+ struct pretty_print_context ctx = {0};
+ struct commit *commit;
+ commit = lookup_commit_reference_by_name(fixup_message);
+ if (!commit)
+ die("could not lookup commit %s", fixup_message);
+ ctx.output_encoding = get_commit_output_encoding();
+ format_commit_message(commit, "fixup! %s\n\n",
+ &sb, &ctx);
+ hook_arg1 = "message";
} else if (!stat(git_path("MERGE_MSG"), &statbuf)) {
if (strbuf_read_file(&sb, git_path("MERGE_MSG"), 0) < 0)
die_errno("could not read MERGE_MSG");
@@ -607,6 +639,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
else if (in_merge)
hook_arg1 = "merge";
+ if (squash_message) {
+ /*
+ * If squash_commit was used for the commit subject,
+ * then we're possibly hijacking other commit log options.
+ * Reset the hook args to tell the real story.
+ */
+ hook_arg1 = "message";
+ hook_arg2 = "";
+ }
+
fp = fopen(git_path(commit_editmsg), "w");
if (fp == NULL)
die_errno("could not open '%s'", git_path(commit_editmsg));
@@ -863,7 +905,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
if (force_author && renew_authorship)
die("Using both --reset-author and --author does not make sense");
- if (logfile || message.len || use_message)
+ if (logfile || message.len || use_message || fixup_message)
use_editor = 0;
if (edit_flag)
use_editor = 1;
@@ -878,48 +920,35 @@ static int parse_and_validate_options(int argc, const char *argv[],
die("You have nothing to amend.");
if (amend && in_merge)
die("You are in the middle of a merge -- cannot amend.");
-
+ if (fixup_message && squash_message)
+ die("Options --squash and --fixup cannot be used together");
if (use_message)
f++;
if (edit_message)
f++;
+ if (fixup_message)
+ f++;
if (logfile)
f++;
if (f > 1)
- die("Only one of -c/-C/-F can be used.");
+ die("Only one of -c/-C/-F/--fixup can be used.");
if (message.len && f > 0)
- die("Option -m cannot be combined with -c/-C/-F.");
+ die("Option -m cannot be combined with -c/-C/-F/--fixup.");
if (edit_message)
use_message = edit_message;
- if (amend && !use_message)
+ if (amend && !use_message && !fixup_message)
use_message = "HEAD";
if (!use_message && renew_authorship)
die("--reset-author can be used only with -C, -c or --amend.");
if (use_message) {
- unsigned char sha1[20];
- static char utf8[] = "UTF-8";
const char *out_enc;
- char *enc, *end;
struct commit *commit;
- if (get_sha1(use_message, sha1))
+ commit = lookup_commit_reference_by_name(use_message);
+ if (!commit)
die("could not lookup commit %s", use_message);
- commit = lookup_commit_reference(sha1);
- if (!commit || parse_commit(commit))
- die("could not parse commit %s", use_message);
-
- enc = strstr(commit->buffer, "\nencoding");
- if (enc) {
- end = strchr(enc + 10, '\n');
- enc = xstrndup(enc + 10, end - (enc + 10));
- } else {
- enc = utf8;
- }
- out_enc = git_commit_encoding ? git_commit_encoding : utf8;
-
- if (strcmp(out_enc, enc))
- use_message_buffer =
- reencode_string(commit->buffer, out_enc, enc);
+ out_enc = get_commit_output_encoding();
+ use_message_buffer = logmsg_reencode(commit, out_enc);
/*
* If we failed to reencode the buffer, just copy it
@@ -929,8 +958,6 @@ static int parse_and_validate_options(int argc, const char *argv[],
*/
if (use_message_buffer == NULL)
use_message_buffer = xstrdup(commit->buffer);
- if (enc != utf8)
- free(enc);
}
if (!!also + !!only + !!all + !!interactive > 1)
@@ -1070,6 +1097,9 @@ int cmd_status(int argc, const char **argv, const char *prefix)
OPT_END(),
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_status_usage, builtin_status_options);
+
if (null_termination && status_format == STATUS_FORMAT_LONG)
status_format = STATUS_FORMAT_PORCELAIN;
@@ -1255,6 +1285,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
int allow_fast_forward = 1;
struct wt_status s;
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_commit_usage, builtin_commit_options);
+
wt_status_prepare(&s);
git_config(git_commit_config, &s);
in_merge = file_exists(git_path("MERGE_HEAD"));
diff --git a/builtin/diff.c b/builtin/diff.c
index a43d326363..945e7583a8 100644
--- a/builtin/diff.c
+++ b/builtin/diff.c
@@ -22,7 +22,7 @@ struct blobinfo {
};
static const char builtin_diff_usage[] =
-"git diff <options> <rev>{0,2} -- <path>*";
+"git diff [<options>] [<commit> [<commit>]] [--] [<path>...]";
static void stuff_change(struct diff_options *opt,
unsigned old_mode, unsigned new_mode,
diff --git a/builtin/gc.c b/builtin/gc.c
index 397a1e6eb3..1a80702b3d 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -189,6 +189,9 @@ int cmd_gc(int argc, const char **argv, const char *prefix)
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_gc_usage, builtin_gc_options);
+
git_config(gc_config, NULL);
if (pack_refs < 0)
diff --git a/builtin/grep.c b/builtin/grep.c
index adb542494d..fdf7131efd 100644
--- a/builtin/grep.c
+++ b/builtin/grep.c
@@ -17,11 +17,7 @@
#include "grep.h"
#include "quote.h"
#include "dir.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
#include "thread-utils.h"
-#endif
static char const * const grep_usage[] = {
"git grep [options] [-e] <pattern> [<rev>...] [[--] <path>...]",
diff --git a/builtin/log.c b/builtin/log.c
index d0297a1c5e..d8c6c28d2f 100644
--- a/builtin/log.c
+++ b/builtin/log.c
@@ -329,8 +329,7 @@ static void show_tagger(char *buf, int len, struct rev_info *rev)
struct strbuf out = STRBUF_INIT;
pp_user_info("Tagger", rev->commit_format, &out, buf, rev->date_mode,
- git_log_output_encoding ?
- git_log_output_encoding: git_commit_encoding);
+ get_log_output_encoding());
printf("%s", out.buf);
strbuf_release(&out);
}
@@ -1159,6 +1158,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)
if (!use_stdout)
output_directory = set_outdir(prefix, output_directory);
+ else
+ setup_pager();
if (output_directory) {
if (use_stdout)
diff --git a/builtin/ls-files.c b/builtin/ls-files.c
index 6a307ab784..fb2d5f4b1f 100644
--- a/builtin/ls-files.c
+++ b/builtin/ls-files.c
@@ -530,6 +530,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
OPT_END()
};
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(ls_files_usage, builtin_ls_files_options);
+
memset(&dir, 0, sizeof(dir));
prefix = cmd_prefix;
if (prefix)
diff --git a/builtin/mailinfo.c b/builtin/mailinfo.c
index 2320d981ce..71e6262a87 100644
--- a/builtin/mailinfo.c
+++ b/builtin/mailinfo.c
@@ -1032,7 +1032,7 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix)
*/
git_config(git_mailinfo_config, NULL);
- def_charset = (git_commit_encoding ? git_commit_encoding : "UTF-8");
+ def_charset = get_commit_output_encoding();
metainfo_charset = def_charset;
while (1 < argc && argv[1][0] == '-') {
diff --git a/builtin/merge.c b/builtin/merge.c
index c24a7be020..42fff387e6 100644
--- a/builtin/merge.c
+++ b/builtin/merge.c
@@ -57,6 +57,7 @@ static const char *branch;
static int option_renormalize;
static int verbosity;
static int allow_rerere_auto;
+static int abort_current_merge;
static struct strategy all_strategy[] = {
{ "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL },
@@ -197,6 +198,8 @@ static struct option builtin_merge_options[] = {
"message to be used for the merge commit (if any)",
option_parse_message),
OPT__VERBOSITY(&verbosity),
+ OPT_BOOLEAN(0, "abort", &abort_current_merge,
+ "abort the current in-progress merge"),
OPT_END()
};
@@ -919,22 +922,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
const char *best_strategy = NULL, *wt_strategy = NULL;
struct commit_list **remotes = &remoteheads;
- if (read_cache_unmerged()) {
- die_resolve_conflict("merge");
- }
- if (file_exists(git_path("MERGE_HEAD"))) {
- /*
- * There is no unmerged entry, don't advise 'git
- * add/rm <file>', just 'git commit'.
- */
- if (advice_resolve_conflict)
- die("You have not concluded your merge (MERGE_HEAD exists).\n"
- "Please, commit your changes before you can merge.");
- else
- die("You have not concluded your merge (MERGE_HEAD exists).");
- }
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage_with_options(builtin_merge_usage, builtin_merge_options);
- resolve_undo_clear();
/*
* Check if we are _not_ on a detached HEAD, i.e. if there is a
* current branch.
@@ -953,6 +943,34 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, builtin_merge_options,
builtin_merge_usage, 0);
+
+ if (abort_current_merge) {
+ int nargc = 2;
+ const char *nargv[] = {"reset", "--merge", NULL};
+
+ if (!file_exists(git_path("MERGE_HEAD")))
+ die("There is no merge to abort (MERGE_HEAD missing).");
+
+ /* Invoke 'git reset --merge' */
+ return cmd_reset(nargc, nargv, prefix);
+ }
+
+ if (read_cache_unmerged())
+ die_resolve_conflict("merge");
+
+ if (file_exists(git_path("MERGE_HEAD"))) {
+ /*
+ * There is no unmerged entry, don't advise 'git
+ * add/rm <file>', just 'git commit'.
+ */
+ if (advice_resolve_conflict)
+ die("You have not concluded your merge (MERGE_HEAD exists).\n"
+ "Please, commit your changes before you can merge.");
+ else
+ die("You have not concluded your merge (MERGE_HEAD exists).");
+ }
+ resolve_undo_clear();
+
if (verbosity < 0)
show_diffstat = 0;
diff --git a/builtin/notes.c b/builtin/notes.c
index c85cbf5a47..4d5556e2cb 100644
--- a/builtin/notes.c
+++ b/builtin/notes.c
@@ -17,6 +17,7 @@
#include "run-command.h"
#include "parse-options.h"
#include "string-list.h"
+#include "notes-merge.h"
static const char * const git_notes_usage[] = {
"git notes [--ref <notes_ref>] [list [<object>]]",
@@ -25,8 +26,12 @@ static const char * const git_notes_usage[] = {
"git notes [--ref <notes_ref>] append [-m <msg> | -F <file> | (-c | -C) <object>] [<object>]",
"git notes [--ref <notes_ref>] edit [<object>]",
"git notes [--ref <notes_ref>] show [<object>]",
+ "git notes [--ref <notes_ref>] merge [-v | -q] [-s <strategy> ] <notes_ref>",
+ "git notes merge --commit [-v | -q]",
+ "git notes merge --abort [-v | -q]",
"git notes [--ref <notes_ref>] remove [<object>]",
"git notes [--ref <notes_ref>] prune [-n | -v]",
+ "git notes [--ref <notes_ref>] get-ref",
NULL
};
@@ -61,6 +66,13 @@ static const char * const git_notes_show_usage[] = {
NULL
};
+static const char * const git_notes_merge_usage[] = {
+ "git notes merge [<options>] <notes_ref>",
+ "git notes merge --commit [<options>]",
+ "git notes merge --abort [<options>]",
+ NULL
+};
+
static const char * const git_notes_remove_usage[] = {
"git notes remove [<object>]",
NULL
@@ -71,6 +83,11 @@ static const char * const git_notes_prune_usage[] = {
NULL
};
+static const char * const git_notes_get_ref_usage[] = {
+ "git notes get-ref",
+ NULL
+};
+
static const char note_template[] =
"\n"
"#\n"
@@ -83,6 +100,16 @@ struct msg_arg {
struct strbuf buf;
};
+static void expand_notes_ref(struct strbuf *sb)
+{
+ if (!prefixcmp(sb->buf, "refs/notes/"))
+ return; /* we're happy */
+ else if (!prefixcmp(sb->buf, "notes/"))
+ strbuf_insert(sb, 0, "refs/", 5);
+ else
+ strbuf_insert(sb, 0, "refs/notes/", 11);
+}
+
static int list_each_note(const unsigned char *object_sha1,
const unsigned char *note_sha1, char *note_path,
void *cb_data)
@@ -271,18 +298,17 @@ static int parse_reedit_arg(const struct option *opt, const char *arg, int unset
return parse_reuse_arg(opt, arg, unset);
}
-int commit_notes(struct notes_tree *t, const char *msg)
+void commit_notes(struct notes_tree *t, const char *msg)
{
- struct commit_list *parent;
- unsigned char tree_sha1[20], prev_commit[20], new_commit[20];
struct strbuf buf = STRBUF_INIT;
+ unsigned char commit_sha1[20];
if (!t)
t = &default_notes_tree;
if (!t->initialized || !t->ref || !*t->ref)
die("Cannot commit uninitialized/unreferenced notes tree");
if (!t->dirty)
- return 0; /* don't have to commit an unchanged tree */
+ return; /* don't have to commit an unchanged tree */
/* Prepare commit message and reflog message */
strbuf_addstr(&buf, "notes: "); /* commit message starts at index 7 */
@@ -290,27 +316,10 @@ int commit_notes(struct notes_tree *t, const char *msg)
if (buf.buf[buf.len - 1] != '\n')
strbuf_addch(&buf, '\n'); /* Make sure msg ends with newline */
- /* Convert notes tree to tree object */
- if (write_notes_tree(t, tree_sha1))
- die("Failed to write current notes tree to database");
-
- /* Create new commit for the tree object */
- if (!read_ref(t->ref, prev_commit)) { /* retrieve parent commit */
- parent = xmalloc(sizeof(*parent));
- parent->item = lookup_commit(prev_commit);
- parent->next = NULL;
- } else {
- hashclr(prev_commit);
- parent = NULL;
- }
- if (commit_tree(buf.buf + 7, tree_sha1, parent, new_commit, NULL))
- die("Failed to commit notes tree to database");
-
- /* Update notes ref with new commit */
- update_ref(buf.buf, t->ref, new_commit, prev_commit, 0, DIE_ON_ERR);
+ create_notes_commit(t, NULL, buf.buf + 7, commit_sha1);
+ update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
strbuf_release(&buf);
- return 0;
}
combine_notes_fn parse_combine_notes_fn(const char *v)
@@ -321,6 +330,8 @@ combine_notes_fn parse_combine_notes_fn(const char *v)
return combine_notes_ignore;
else if (!strcasecmp(v, "concatenate"))
return combine_notes_concatenate;
+ else if (!strcasecmp(v, "cat_sort_uniq"))
+ return combine_notes_cat_sort_uniq;
else
return NULL;
}
@@ -573,8 +584,8 @@ static int add(int argc, const char **argv, const char *prefix)
if (is_null_sha1(new_note))
remove_note(t, object);
- else
- add_note(t, object, new_note, combine_notes_overwrite);
+ else if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", "add");
@@ -653,7 +664,8 @@ static int copy(int argc, const char **argv, const char *prefix)
goto out;
}
- add_note(t, object, from_note, combine_notes_overwrite);
+ if (add_note(t, object, from_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
commit_notes(t, "Notes added by 'git notes copy'");
out:
free_notes(t);
@@ -712,8 +724,8 @@ static int append_edit(int argc, const char **argv, const char *prefix)
if (is_null_sha1(new_note))
remove_note(t, object);
- else
- add_note(t, object, new_note, combine_notes_overwrite);
+ else if (add_note(t, object, new_note, combine_notes_overwrite))
+ die("BUG: combine_notes_overwrite failed");
snprintf(logmsg, sizeof(logmsg), "Notes %s by 'git notes %s'",
is_null_sha1(new_note) ? "removed" : "added", argv[0]);
@@ -761,6 +773,180 @@ static int show(int argc, const char **argv, const char *prefix)
return retval;
}
+static int merge_abort(struct notes_merge_options *o)
+{
+ int ret = 0;
+
+ /*
+ * Remove .git/NOTES_MERGE_PARTIAL and .git/NOTES_MERGE_REF, and call
+ * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
+ */
+
+ if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
+ ret += error("Failed to delete ref NOTES_MERGE_PARTIAL");
+ if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
+ ret += error("Failed to delete ref NOTES_MERGE_REF");
+ if (notes_merge_abort(o))
+ ret += error("Failed to remove 'git notes merge' worktree");
+ return ret;
+}
+
+static int merge_commit(struct notes_merge_options *o)
+{
+ struct strbuf msg = STRBUF_INIT;
+ unsigned char sha1[20], parent_sha1[20];
+ struct notes_tree *t;
+ struct commit *partial;
+ struct pretty_print_context pretty_ctx;
+
+ /*
+ * Read partial merge result from .git/NOTES_MERGE_PARTIAL,
+ * and target notes ref from .git/NOTES_MERGE_REF.
+ */
+
+ if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
+ die("Failed to read ref NOTES_MERGE_PARTIAL");
+ else if (!(partial = lookup_commit_reference(sha1)))
+ die("Could not find commit from NOTES_MERGE_PARTIAL.");
+ else if (parse_commit(partial))
+ die("Could not parse commit from NOTES_MERGE_PARTIAL.");
+
+ if (partial->parents)
+ hashcpy(parent_sha1, partial->parents->item->object.sha1);
+ else
+ hashclr(parent_sha1);
+
+ t = xcalloc(1, sizeof(struct notes_tree));
+ init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
+
+ o->local_ref = resolve_ref("NOTES_MERGE_REF", sha1, 0, 0);
+ if (!o->local_ref)
+ die("Failed to resolve NOTES_MERGE_REF");
+
+ if (notes_merge_commit(o, t, partial, sha1))
+ die("Failed to finalize notes merge");
+
+ /* Reuse existing commit message in reflog message */
+ memset(&pretty_ctx, 0, sizeof(pretty_ctx));
+ format_commit_message(partial, "%s", &msg, &pretty_ctx);
+ strbuf_trim(&msg);
+ strbuf_insert(&msg, 0, "notes: ", 7);
+ update_ref(msg.buf, o->local_ref, sha1,
+ is_null_sha1(parent_sha1) ? NULL : parent_sha1,
+ 0, DIE_ON_ERR);
+
+ free_notes(t);
+ strbuf_release(&msg);
+ return merge_abort(o);
+}
+
+static int merge(int argc, const char **argv, const char *prefix)
+{
+ struct strbuf remote_ref = STRBUF_INIT, msg = STRBUF_INIT;
+ unsigned char result_sha1[20];
+ struct notes_tree *t;
+ struct notes_merge_options o;
+ int do_merge = 0, do_commit = 0, do_abort = 0;
+ int verbosity = 0, result;
+ const char *strategy = NULL;
+ struct option options[] = {
+ OPT_GROUP("General options"),
+ OPT__VERBOSITY(&verbosity),
+ OPT_GROUP("Merge options"),
+ OPT_STRING('s', "strategy", &strategy, "strategy",
+ "resolve notes conflicts using the given strategy "
+ "(manual/ours/theirs/union/cat_sort_uniq)"),
+ OPT_GROUP("Committing unmerged notes"),
+ { OPTION_BOOLEAN, 0, "commit", &do_commit, NULL,
+ "finalize notes merge by committing unmerged notes",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_GROUP("Aborting notes merge resolution"),
+ { OPTION_BOOLEAN, 0, "abort", &do_abort, NULL,
+ "abort notes merge",
+ PARSE_OPT_NOARG | PARSE_OPT_NONEG },
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, prefix, options,
+ git_notes_merge_usage, 0);
+
+ if (strategy || do_commit + do_abort == 0)
+ do_merge = 1;
+ if (do_merge + do_commit + do_abort != 1) {
+ error("cannot mix --commit, --abort or -s/--strategy");
+ usage_with_options(git_notes_merge_usage, options);
+ }
+
+ if (do_merge && argc != 1) {
+ error("Must specify a notes ref to merge");
+ usage_with_options(git_notes_merge_usage, options);
+ } else if (!do_merge && argc) {
+ error("too many parameters");
+ usage_with_options(git_notes_merge_usage, options);
+ }
+
+ init_notes_merge_options(&o);
+ o.verbosity = verbosity + NOTES_MERGE_VERBOSITY_DEFAULT;
+
+ if (do_abort)
+ return merge_abort(&o);
+ if (do_commit)
+ return merge_commit(&o);
+
+ o.local_ref = default_notes_ref();
+ strbuf_addstr(&remote_ref, argv[0]);
+ expand_notes_ref(&remote_ref);
+ o.remote_ref = remote_ref.buf;
+
+ if (strategy) {
+ if (!strcmp(strategy, "manual"))
+ o.strategy = NOTES_MERGE_RESOLVE_MANUAL;
+ else if (!strcmp(strategy, "ours"))
+ o.strategy = NOTES_MERGE_RESOLVE_OURS;
+ else if (!strcmp(strategy, "theirs"))
+ o.strategy = NOTES_MERGE_RESOLVE_THEIRS;
+ else if (!strcmp(strategy, "union"))
+ o.strategy = NOTES_MERGE_RESOLVE_UNION;
+ else if (!strcmp(strategy, "cat_sort_uniq"))
+ o.strategy = NOTES_MERGE_RESOLVE_CAT_SORT_UNIQ;
+ else {
+ error("Unknown -s/--strategy: %s", strategy);
+ usage_with_options(git_notes_merge_usage, options);
+ }
+ }
+
+ t = init_notes_check("merge");
+
+ strbuf_addf(&msg, "notes: Merged notes from %s into %s",
+ remote_ref.buf, default_notes_ref());
+ strbuf_add(&(o.commit_msg), msg.buf + 7, msg.len - 7); /* skip "notes: " */
+
+ result = notes_merge(&o, t, result_sha1);
+
+ if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
+ /* Update default notes ref with new commit */
+ update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
+ 0, DIE_ON_ERR);
+ else { /* Merge has unresolved conflicts */
+ /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
+ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
+ 0, DIE_ON_ERR);
+ /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
+ if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
+ die("Failed to store link to current notes ref (%s)",
+ default_notes_ref());
+ printf("Automatic notes merge failed. Fix conflicts in %s and "
+ "commit the result with 'git notes merge --commit', or "
+ "abort the merge with 'git notes merge --abort'.\n",
+ git_path(NOTES_MERGE_WORKTREE));
+ }
+
+ free_notes(t);
+ strbuf_release(&remote_ref);
+ strbuf_release(&msg);
+ return result < 0; /* return non-zero on conflicts */
+}
+
static int remove_cmd(int argc, const char **argv, const char *prefix)
{
struct option options[] = {
@@ -827,6 +1013,21 @@ static int prune(int argc, const char **argv, const char *prefix)
return 0;
}
+static int get_ref(int argc, const char **argv, const char *prefix)
+{
+ struct option options[] = { OPT_END() };
+ argc = parse_options(argc, argv, prefix, options,
+ git_notes_get_ref_usage, 0);
+
+ if (argc) {
+ error("too many parameters");
+ usage_with_options(git_notes_get_ref_usage, options);
+ }
+
+ puts(default_notes_ref());
+ return 0;
+}
+
int cmd_notes(int argc, const char **argv, const char *prefix)
{
int result;
@@ -843,13 +1044,8 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
if (override_notes_ref) {
struct strbuf sb = STRBUF_INIT;
- if (!prefixcmp(override_notes_ref, "refs/notes/"))
- /* we're happy */;
- else if (!prefixcmp(override_notes_ref, "notes/"))
- strbuf_addstr(&sb, "refs/");
- else
- strbuf_addstr(&sb, "refs/notes/");
strbuf_addstr(&sb, override_notes_ref);
+ expand_notes_ref(&sb);
setenv("GIT_NOTES_REF", sb.buf, 1);
strbuf_release(&sb);
}
@@ -864,10 +1060,14 @@ int cmd_notes(int argc, const char **argv, const char *prefix)
result = append_edit(argc, argv, prefix);
else if (!strcmp(argv[0], "show"))
result = show(argc, argv, prefix);
+ else if (!strcmp(argv[0], "merge"))
+ result = merge(argc, argv, prefix);
else if (!strcmp(argv[0], "remove"))
result = remove_cmd(argc, argv, prefix);
else if (!strcmp(argv[0], "prune"))
result = prune(argc, argv, prefix);
+ else if (!strcmp(argv[0], "get-ref"))
+ result = get_ref(argc, argv, prefix);
else {
result = error("Unknown subcommand: %s", argv[0]);
usage_with_options(git_notes_usage, options);
diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c
index f8eba53c82..b0503b202a 100644
--- a/builtin/pack-objects.c
+++ b/builtin/pack-objects.c
@@ -16,11 +16,7 @@
#include "list-objects.h"
#include "progress.h"
#include "refs.h"
-
-#ifndef NO_PTHREADS
-#include <pthread.h>
#include "thread-utils.h"
-#endif
static const char pack_usage[] =
"git pack-objects [ -q | --progress | --all-progress ]\n"
@@ -1298,9 +1294,23 @@ static int try_delta(struct unpacked *trg, struct unpacked *src,
read_lock();
src->data = read_sha1_file(src_entry->idx.sha1, &type, &sz);
read_unlock();
- if (!src->data)
+ if (!src->data) {
+ if (src_entry->preferred_base) {
+ static int warned = 0;
+ if (!warned++)
+ warning("object %s cannot be read",
+ sha1_to_hex(src_entry->idx.sha1));
+ /*
+ * Those objects are not included in the
+ * resulting pack. Be resilient and ignore
+ * them if they can't be read, in case the
+ * pack could be created nevertheless.
+ */
+ return 0;
+ }
die("object %s cannot be read",
sha1_to_hex(src_entry->idx.sha1));
+ }
if (sz != src_size)
die("object %s inconsistent object length (%lu vs %lu)",
sha1_to_hex(src_entry->idx.sha1), sz, src_size);
@@ -1529,7 +1539,7 @@ static void try_to_free_from_threads(size_t size)
read_unlock();
}
-try_to_free_t old_try_to_free_routine;
+static try_to_free_t old_try_to_free_routine;
/*
* The main thread waits on the condition that (at least) one of the workers
diff --git a/builtin/remote-ext.c b/builtin/remote-ext.c
new file mode 100644
index 0000000000..1f773171cb
--- /dev/null
+++ b/builtin/remote-ext.c
@@ -0,0 +1,246 @@
+#include "git-compat-util.h"
+#include "transport.h"
+#include "run-command.h"
+
+/*
+ * URL syntax:
+ * 'command [arg1 [arg2 [...]]]' Invoke command with given arguments.
+ * Special characters:
+ * '% ': Literal space in argument.
+ * '%%': Literal percent sign.
+ * '%S': Name of service (git-upload-pack/git-upload-archive/
+ * git-receive-pack.
+ * '%s': Same as \s, but with possible git- prefix stripped.
+ * '%G': Only allowed as first 'character' of argument. Do not pass this
+ * Argument to command, instead send this as name of repository
+ * in in-line git://-style request (also activates sending this
+ * style of request).
+ * '%V': Only allowed as first 'character' of argument. Used in
+ * conjunction with '%G': Do not pass this argument to command,
+ * instead send this as vhost in git://-style request (note: does
+ * not activate sending git:// style request).
+ */
+
+static char *git_req;
+static char *git_req_vhost;
+
+static char *strip_escapes(const char *str, const char *service,
+ const char **next)
+{
+ size_t rpos = 0;
+ int escape = 0;
+ char special = 0;
+ size_t pslen = 0;
+ size_t pSlen = 0;
+ size_t psoff = 0;
+ struct strbuf ret = STRBUF_INIT;
+
+ /* Calculate prefix length for \s and lengths for \s and \S */
+ if (!strncmp(service, "git-", 4))
+ psoff = 4;
+ pSlen = strlen(service);
+ pslen = pSlen - psoff;
+
+ /* Pass the service to command. */
+ setenv("GIT_EXT_SERVICE", service, 1);
+ setenv("GIT_EXT_SERVICE_NOPREFIX", service + psoff, 1);
+
+ /* Scan the length of argument. */
+ while (str[rpos] && (escape || str[rpos] != ' ')) {
+ if (escape) {
+ switch (str[rpos]) {
+ case ' ':
+ case '%':
+ case 's':
+ case 'S':
+ break;
+ case 'G':
+ case 'V':
+ special = str[rpos];
+ if (rpos == 1)
+ break;
+ /* Fall-through to error. */
+ default:
+ die("Bad remote-ext placeholder '%%%c'.",
+ str[rpos]);
+ }
+ escape = 0;
+ } else
+ escape = (str[rpos] == '%');
+ rpos++;
+ }
+ if (escape && !str[rpos])
+ die("remote-ext command has incomplete placeholder");
+ *next = str + rpos;
+ if (**next == ' ')
+ ++*next; /* Skip over space */
+
+ /*
+ * Do the actual placeholder substitution. The string will be short
+ * enough not to overflow integers.
+ */
+ rpos = special ? 2 : 0; /* Skip first 2 bytes in specials. */
+ escape = 0;
+ while (str[rpos] && (escape || str[rpos] != ' ')) {
+ if (escape) {
+ switch (str[rpos]) {
+ case ' ':
+ case '%':
+ strbuf_addch(&ret, str[rpos]);
+ break;
+ case 's':
+ strbuf_addstr(&ret, service + psoff);
+ break;
+ case 'S':
+ strbuf_addstr(&ret, service);
+ break;
+ }
+ escape = 0;
+ } else
+ switch (str[rpos]) {
+ case '%':
+ escape = 1;
+ break;
+ default:
+ strbuf_addch(&ret, str[rpos]);
+ break;
+ }
+ rpos++;
+ }
+ switch (special) {
+ case 'G':
+ git_req = strbuf_detach(&ret, NULL);
+ return NULL;
+ case 'V':
+ git_req_vhost = strbuf_detach(&ret, NULL);
+ return NULL;
+ default:
+ return strbuf_detach(&ret, NULL);
+ }
+}
+
+/* Should be enough... */
+#define MAXARGUMENTS 256
+
+static const char **parse_argv(const char *arg, const char *service)
+{
+ int arguments = 0;
+ int i;
+ const char **ret;
+ char *temparray[MAXARGUMENTS + 1];
+
+ while (*arg) {
+ char *expanded;
+ if (arguments == MAXARGUMENTS)
+ die("remote-ext command has too many arguments");
+ expanded = strip_escapes(arg, service, &arg);
+ if (expanded)
+ temparray[arguments++] = expanded;
+ }
+
+ ret = xmalloc((arguments + 1) * sizeof(char *));
+ for (i = 0; i < arguments; i++)
+ ret[i] = temparray[i];
+ ret[arguments] = NULL;
+ return ret;
+}
+
+static void send_git_request(int stdin_fd, const char *serv, const char *repo,
+ const char *vhost)
+{
+ size_t bufferspace;
+ size_t wpos = 0;
+ char *buffer;
+
+ /*
+ * Request needs 12 bytes extra if there is vhost (xxxx \0host=\0) and
+ * 6 bytes extra (xxxx \0) if there is no vhost.
+ */
+ if (vhost)
+ bufferspace = strlen(serv) + strlen(repo) + strlen(vhost) + 12;
+ else
+ bufferspace = strlen(serv) + strlen(repo) + 6;
+
+ if (bufferspace > 0xFFFF)
+ die("Request too large to send");
+ buffer = xmalloc(bufferspace);
+
+ /* Make the packet. */
+ wpos = sprintf(buffer, "%04x%s %s%c", (unsigned)bufferspace,
+ serv, repo, 0);
+
+ /* Add vhost if any. */
+ if (vhost)
+ sprintf(buffer + wpos, "host=%s%c", vhost, 0);
+
+ /* Send the request */
+ if (write_in_full(stdin_fd, buffer, bufferspace) < 0)
+ die_errno("Failed to send request");
+
+ free(buffer);
+}
+
+static int run_child(const char *arg, const char *service)
+{
+ int r;
+ struct child_process child;
+
+ memset(&child, 0, sizeof(child));
+ child.in = -1;
+ child.out = -1;
+ child.err = 0;
+ child.argv = parse_argv(arg, service);
+
+ if (start_command(&child) < 0)
+ die("Can't run specified command");
+
+ if (git_req)
+ send_git_request(child.in, service, git_req, git_req_vhost);
+
+ r = bidirectional_transfer_loop(child.out, child.in);
+ if (!r)
+ r = finish_command(&child);
+ else
+ finish_command(&child);
+ return r;
+}
+
+#define MAXCOMMAND 4096
+
+static int command_loop(const char *child)
+{
+ char buffer[MAXCOMMAND];
+
+ while (1) {
+ size_t length;
+ if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+ if (ferror(stdin))
+ die("Comammand input error");
+ exit(0);
+ }
+ /* Strip end of line characters. */
+ length = strlen(buffer);
+ while (isspace((unsigned char)buffer[length - 1]))
+ buffer[--length] = 0;
+
+ if (!strcmp(buffer, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!strncmp(buffer, "connect ", 8)) {
+ printf("\n");
+ fflush(stdout);
+ return run_child(child, buffer + 8);
+ } else {
+ fprintf(stderr, "Bad command");
+ return 1;
+ }
+ }
+}
+
+int cmd_remote_ext(int argc, const char **argv, const char *prefix)
+{
+ if (argc != 3)
+ die("Expected two arguments");
+
+ return command_loop(argv[2]);
+}
diff --git a/builtin/remote-fd.c b/builtin/remote-fd.c
new file mode 100644
index 0000000000..1f2467bdb7
--- /dev/null
+++ b/builtin/remote-fd.c
@@ -0,0 +1,79 @@
+#include "git-compat-util.h"
+#include "transport.h"
+
+/*
+ * URL syntax:
+ * 'fd::<inoutfd>[/<anything>]' Read/write socket pair
+ * <inoutfd>.
+ * 'fd::<infd>,<outfd>[/<anything>]' Read pipe <infd> and write
+ * pipe <outfd>.
+ * [foo] indicates 'foo' is optional. <anything> is any string.
+ *
+ * The data output to <outfd>/<inoutfd> should be passed unmolested to
+ * git-receive-pack/git-upload-pack/git-upload-archive and output of
+ * git-receive-pack/git-upload-pack/git-upload-archive should be passed
+ * unmolested to <infd>/<inoutfd>.
+ *
+ */
+
+#define MAXCOMMAND 4096
+
+static void command_loop(int input_fd, int output_fd)
+{
+ char buffer[MAXCOMMAND];
+
+ while (1) {
+ size_t i;
+ if (!fgets(buffer, MAXCOMMAND - 1, stdin)) {
+ if (ferror(stdin))
+ die("Input error");
+ return;
+ }
+ /* Strip end of line characters. */
+ i = strlen(buffer);
+ while (i > 0 && isspace(buffer[i - 1]))
+ buffer[--i] = 0;
+
+ if (!strcmp(buffer, "capabilities")) {
+ printf("*connect\n\n");
+ fflush(stdout);
+ } else if (!strncmp(buffer, "connect ", 8)) {
+ printf("\n");
+ fflush(stdout);
+ if (bidirectional_transfer_loop(input_fd,
+ output_fd))
+ die("Copying data between file descriptors failed");
+ return;
+ } else {
+ die("Bad command: %s", buffer);
+ }
+ }
+}
+
+int cmd_remote_fd(int argc, const char **argv, const char *prefix)
+{
+ int input_fd = -1;
+ int output_fd = -1;
+ char *end;
+
+ if (argc != 3)
+ die("Expected two arguments");
+
+ input_fd = (int)strtoul(argv[2], &end, 10);
+
+ if ((end == argv[2]) || (*end != ',' && *end != '/' && *end))
+ die("Bad URL syntax");
+
+ if (*end == '/' || !*end) {
+ output_fd = input_fd;
+ } else {
+ char *end2;
+ output_fd = (int)strtoul(end + 1, &end2, 10);
+
+ if ((end2 == end + 1) || (*end2 != '/' && *end2))
+ die("Bad URL syntax");
+ }
+
+ command_loop(input_fd, output_fd);
+ return 0;
+}
diff --git a/builtin/revert.c b/builtin/revert.c
index 57b51e4a0e..bb6e9e83b7 100644
--- a/builtin/revert.c
+++ b/builtin/revert.c
@@ -547,6 +547,21 @@ static void prepare_revs(struct rev_info *revs)
die("empty commit set passed");
}
+static void read_and_refresh_cache(const char *me)
+{
+ static struct lock_file index_lock;
+ int index_fd = hold_locked_index(&index_lock, 0);
+ if (read_index_preload(&the_index, NULL) < 0)
+ die("git %s: failed to read the index", me);
+ refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, NULL, NULL, NULL);
+ if (the_index.cache_changed) {
+ if (write_index(&the_index, index_fd) ||
+ commit_locked_index(&index_lock))
+ die("git %s: failed to refresh the index", me);
+ }
+ rollback_lock_file(&index_lock);
+}
+
static int revert_or_cherry_pick(int argc, const char **argv)
{
struct rev_info revs;
@@ -567,8 +582,7 @@ static int revert_or_cherry_pick(int argc, const char **argv)
die("cherry-pick --ff cannot be used with --edit");
}
- if (read_cache() < 0)
- die("git %s: failed to read the index", me);
+ read_and_refresh_cache(me);
prepare_revs(&revs);
diff --git a/builtin/tag.c b/builtin/tag.c
index d1d7d8701d..aa1f87d47a 100644
--- a/builtin/tag.c
+++ b/builtin/tag.c
@@ -29,8 +29,6 @@ struct tag_filter {
struct commit_list *with_commit;
};
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
static int show_reference(const char *refname, const unsigned char *sha1,
int flag, void *cb_data)
{
@@ -70,9 +68,9 @@ static int show_reference(const char *refname, const unsigned char *sha1,
return 0;
}
/* only take up to "lines" lines, and strip the signature */
+ size = parse_signature(buf, size);
for (i = 0, sp += 2;
- i < filter->lines && sp < buf + size &&
- prefixcmp(sp, PGP_SIGNATURE "\n");
+ i < filter->lines && sp < buf + size;
i++) {
if (i)
printf("\n ");
@@ -242,8 +240,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
{
unsigned long size;
enum object_type type;
- char *buf, *sp, *eob;
- size_t len;
+ char *buf, *sp;
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
@@ -256,12 +253,7 @@ static void write_tag_body(int fd, const unsigned char *sha1)
return;
}
sp += 2; /* skip the 2 LFs */
- eob = strstr(sp, "\n" PGP_SIGNATURE "\n");
- if (eob)
- len = eob - sp;
- else
- len = buf + size - sp;
- write_or_die(fd, sp, len);
+ write_or_die(fd, sp, parse_signature(sp, buf + size - sp));
free(buf);
}
diff --git a/builtin/update-index.c b/builtin/update-index.c
index 62d9f3f0fa..200c7efed1 100644
--- a/builtin/update-index.c
+++ b/builtin/update-index.c
@@ -589,6 +589,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
int lock_error = 0;
struct lock_file *lock_file;
+ if (argc == 2 && !strcmp(argv[1], "-h"))
+ usage(update_index_usage);
+
git_config(git_default_config, NULL);
/* We can't free this memory, it becomes part of a linked list parsed atexit() */
diff --git a/builtin/verify-tag.c b/builtin/verify-tag.c
index 8136dba7a1..3134766049 100644
--- a/builtin/verify-tag.c
+++ b/builtin/verify-tag.c
@@ -17,13 +17,11 @@ static const char * const verify_tag_usage[] = {
NULL
};
-#define PGP_SIGNATURE "-----BEGIN PGP SIGNATURE-----"
-
static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
{
struct child_process gpg;
const char *args_gpg[] = {"gpg", "--verify", "FILE", "-", NULL};
- char path[PATH_MAX], *eol;
+ char path[PATH_MAX];
size_t len;
int fd, ret;
@@ -37,11 +35,7 @@ static int run_gpg_verify(const char *buf, unsigned long size, int verbose)
close(fd);
/* find the length without signature */
- len = 0;
- while (len < size && prefixcmp(buf + len, PGP_SIGNATURE)) {
- eol = memchr(buf + len, '\n', size - len);
- len += eol ? eol - (buf + len) + 1 : size - len;
- }
+ len = parse_signature(buf, size);
if (verbose)
write_in_full(1, buf, len);