提交 b240ea25 编写于 作者: J Jiang Xin

Merge maint branch for tracking l10n updates of git stable version

Use master branch to track l10n updates for git next release, while
use maint branch to track l10n updates for git stable version.
Git v1.7.10.1 Release Notes
===========================
Fixes since v1.7.10
-------------------
* "git add -p" is not designed to deal with unmerged paths but did
not exclude them and tried to apply funny patches only to fail.
* When PATH contains an unreadable directory, alias expansion code
did not kick in, and failed with an error that said "git-subcmd"
was not found.
* "git clean -d -f" (not "-d -f -f") is supposed to protect nested
working trees of independent git repositories that exist in the
current project working tree from getting removed, but the
protection applied only to such working trees that are at the
top-level of the current project by mistake.
* "git commit --author=$name" did not tell the name that was being
recorded in the resulting commit to hooks, even though it does do
so when the end user overrode the authorship via the
"GIT_AUTHOR_NAME" environment variable.
* When "git commit --template F" errors out because the user did not
touch the message, it claimed that it aborts due to "empty
message", which was utterly wrong.
* The regexp configured with diff.wordregex was incorrectly reused
across files.
* An age-old corner case bug in combine diff (only triggered with -U0
and the hunk at the beginning of the file needs to be shown) has
been fixed.
* Rename detection logic used to match two empty files as renames
during merge-recursive, leading to unnatural mismerges.
* Running "notes merge --commit" failed to perform correctly when run
from any directory inside $GIT_DIR/. When "notes merge" stops with
conflicts, $GIT_DIR/NOTES_MERGE_WORKTREE is the place a user edits
to resolve it.
* The 'push to upstream' implementation was broken in some corner
cases. "git push $there" without refspec, when the current branch
is set to push to a remote different from $there, used to push to
$there using the upstream information to a remote unreleated to
$there.
Also contains minor fixes and documentation updates.
Git v1.7.7.7 Release Notes
==========================
Fixes since v1.7.7.6
--------------------
* An error message from 'git bundle' had an unmatched single quote pair in it.
* 'git diff --histogram' option was not described.
* 'git imap-send' carried an unused dead code.
Also contains minor fixes and documentation updates.
Git v1.7.8.6 Release Notes
==========================
Fixes since v1.7.8.5
--------------------
* An error message from 'git bundle' had an unmatched single quote pair in it.
* 'git diff --histogram' option was not described.
* Documentation for 'git rev-list' had minor formatting errors.
* 'git imap-send' carried an unused dead code.
* The way 'git fetch' implemented its connectivity check over
received objects was overly pessimistic, and wasted a lot of
cycles.
* Various minor backports of fixes from the 'master' and the 'maint'
branch.
Also contains minor fixes and documentation updates.
Git v1.7.9.7 Release Notes
==========================
Fixes since v1.7.9.6
--------------------
* An error message from 'git bundle' had an unmatched single quote pair in it.
* The way 'git fetch' implemented its connectivity check over
received objects was overly pessimistic, and wasted a lot of
cycles.
Also contains minor fixes and documentation updates.
......@@ -132,11 +132,14 @@ OPTIONS
-t <file>::
--template=<file>::
Use the contents of the given file as the initial version
of the commit message. The editor is invoked and you can
make subsequent changes. If a message is specified using
the `-m` or `-F` options, this option has no effect. This
overrides the `commit.template` configuration variable.
When editing the commit message, start the editor with the
contents in the given file. The `commit.template` configuration
variable is often used to give this option implicitly to the
command. This mechanism can be used by projects that want to
guide participants with some hints on what to write in the message
in what order. If the user exits the editor without editing the
message, the commit is aborted. This has no effect when a message
is given by other means, e.g. with the `-m` or `-F` options.
-s::
--signoff::
......
......@@ -49,9 +49,10 @@ Documentation for older releases are available here:
* release notes for
link:RelNotes/1.7.10.txt[1.7.10].
* link:v1.7.9.6/git.html[documentation for release 1.7.9.6]
* link:v1.7.9.7/git.html[documentation for release 1.7.9.7]
* release notes for
link:RelNotes/1.7.9.7.txt[1.7.9.7],
link:RelNotes/1.7.9.6.txt[1.7.9.6],
link:RelNotes/1.7.9.5.txt[1.7.9.5],
link:RelNotes/1.7.9.4.txt[1.7.9.4],
......@@ -60,9 +61,10 @@ Documentation for older releases are available here:
link:RelNotes/1.7.9.1.txt[1.7.9.1],
link:RelNotes/1.7.9.txt[1.7.9].
* link:v1.7.8.5/git.html[documentation for release 1.7.8.5]
* link:v1.7.8.6/git.html[documentation for release 1.7.8.6]
* release notes for
link:RelNotes/1.7.8.6.txt[1.7.8.6],
link:RelNotes/1.7.8.5.txt[1.7.8.5],
link:RelNotes/1.7.8.4.txt[1.7.8.4],
link:RelNotes/1.7.8.3.txt[1.7.8.3],
......@@ -70,9 +72,10 @@ Documentation for older releases are available here:
link:RelNotes/1.7.8.1.txt[1.7.8.1],
link:RelNotes/1.7.8.txt[1.7.8].
* link:v1.7.7.6/git.html[documentation for release 1.7.7.6]
* link:v1.7.7.7/git.html[documentation for release 1.7.7.7]
* release notes for
link:RelNotes/1.7.7.7.txt[1.7.7.7],
link:RelNotes/1.7.7.6.txt[1.7.7.6],
link:RelNotes/1.7.7.5.txt[1.7.7.5],
link:RelNotes/1.7.7.4.txt[1.7.7.4],
......
Documentation/RelNotes/1.7.10.txt
\ No newline at end of file
Documentation/RelNotes/1.7.10.1.txt
\ No newline at end of file
......@@ -533,9 +533,20 @@ static int is_a_merge(const struct commit *current_head)
static const char sign_off_header[] = "Signed-off-by: ";
static void export_one(const char *var, const char *s, const char *e, int hack)
{
struct strbuf buf = STRBUF_INIT;
if (hack)
strbuf_addch(&buf, hack);
strbuf_addf(&buf, "%.*s", (int)(e - s), s);
setenv(var, buf.buf, 1);
strbuf_release(&buf);
}
static void determine_author_info(struct strbuf *author_ident)
{
char *name, *email, *date;
struct ident_split author;
name = getenv("GIT_AUTHOR_NAME");
email = getenv("GIT_AUTHOR_EMAIL");
......@@ -585,6 +596,11 @@ static void determine_author_info(struct strbuf *author_ident)
date = force_date;
strbuf_addstr(author_ident, fmt_ident(name, email, date,
IDENT_ERROR_ON_NO_NAME));
if (!split_ident_line(&author, author_ident->buf, author_ident->len)) {
export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
}
}
static int ends_rfc2822_footer(struct strbuf *sb)
......@@ -652,6 +668,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
int ident_shown = 0;
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
if (!no_verify && run_hook(index_file, "pre-commit", NULL))
return 0;
......@@ -771,9 +790,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
strbuf_release(&sb);
/* This checks and barfs if author is badly specified */
determine_author_info(author_ident);
/* This checks if committer ident is explicitly given */
strbuf_addstr(&committer_ident, git_committer_info(0));
if (use_editor && include_status) {
......@@ -905,27 +921,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
return 1;
}
/*
* Find out if the message in the strbuf contains only whitespace and
* Signed-off-by lines.
*/
static int message_is_empty(struct strbuf *sb)
static int rest_is_empty(struct strbuf *sb, int start)
{
struct strbuf tmpl = STRBUF_INIT;
int i, eol;
const char *nl;
int eol, i, start = 0;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
/* See if the template is just a prefix of the message. */
if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
if (start + tmpl.len <= sb->len &&
memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
start += tmpl.len;
}
strbuf_release(&tmpl);
/* Check if the rest is just whitespace and Signed-of-by's. */
for (i = start; i < sb->len; i++) {
......@@ -948,6 +947,40 @@ static int message_is_empty(struct strbuf *sb)
return 1;
}
/*
* Find out if the message in the strbuf contains only whitespace and
* Signed-off-by lines.
*/
static int message_is_empty(struct strbuf *sb)
{
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
return rest_is_empty(sb, 0);
}
/*
* See if the user edited the message in the editor or left what
* was in the template intact
*/
static int template_untouched(struct strbuf *sb)
{
struct strbuf tmpl = STRBUF_INIT;
char *start;
if (cleanup_mode == CLEANUP_NONE && sb->len)
return 0;
if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
return 0;
stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
start = (char *)skip_prefix(sb->buf, tmpl.buf);
if (!start)
start = sb->buf;
strbuf_release(&tmpl);
return rest_is_empty(sb, start - sb->buf);
}
static const char *find_author_by_nickname(const char *name)
{
struct rev_info revs;
......@@ -1055,6 +1088,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
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/--fixup.")));
if (f || message.len)
template_file = NULL;
if (edit_message)
use_message = edit_message;
if (amend && !use_message && !fixup_message)
......@@ -1494,6 +1529,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
if (cleanup_mode != CLEANUP_NONE)
stripspace(&sb, cleanup_mode == CLEANUP_ALL);
if (template_untouched(&sb) && !allow_empty_message) {
rollback_index_files();
fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
exit(1);
}
if (message_is_empty(&sb) && !allow_empty_message) {
rollback_index_files();
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
......
......@@ -327,7 +327,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix)
add_head_to_pending(&rev);
if (!rev.pending.nr) {
struct tree *tree;
tree = lookup_tree((const unsigned char*)EMPTY_TREE_SHA1_BIN);
tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
add_pending_object(&rev, &tree->object, "HEAD");
}
break;
......
......@@ -65,6 +65,16 @@ static void set_refspecs(const char **refs, int nr)
}
}
static int push_url_of_remote(struct remote *remote, const char ***url_p)
{
if (remote->pushurl_nr) {
*url_p = remote->pushurl;
return remote->pushurl_nr;
}
*url_p = remote->url;
return remote->url_nr;
}
static void setup_push_upstream(struct remote *remote)
{
struct strbuf refspec = STRBUF_INIT;
......@@ -76,7 +86,7 @@ static void setup_push_upstream(struct remote *remote)
"\n"
" git push %s HEAD:<name-of-remote-branch>\n"),
remote->name);
if (!branch->merge_nr || !branch->merge)
if (!branch->merge_nr || !branch->merge || !branch->remote_name)
die(_("The current branch %s has no upstream branch.\n"
"To push the current branch and set the remote as upstream, use\n"
"\n"
......@@ -87,6 +97,12 @@ static void setup_push_upstream(struct remote *remote)
if (branch->merge_nr != 1)
die(_("The current branch %s has multiple upstream branches, "
"refusing to push."), branch->name);
if (strcmp(branch->remote_name, remote->name))
die(_("You are pushing to remote '%s', which is not the upstream of\n"
"your current branch '%s', without telling me what to push\n"
"to update which remote branch."),
remote->name, branch->name);
strbuf_addf(&refspec, "%s:%s", branch->name, branch->merge[0]->src);
add_refspec(refspec.buf);
}
......@@ -196,13 +212,7 @@ static int do_push(const char *repo, int flags)
setup_default_push_refspecs(remote);
}
errs = 0;
if (remote->pushurl_nr) {
url = remote->pushurl;
url_nr = remote->pushurl_nr;
} else {
url = remote->url;
url_nr = remote->url_nr;
}
url_nr = push_url_of_remote(remote, &url);
if (url_nr) {
for (i = 0; i < url_nr; i++) {
struct transport *transport =
......
......@@ -289,7 +289,7 @@ int create_bundle(struct bundle_header *header, const char *path,
argc = setup_revisions(argc, argv, &revs, NULL);
if (argc > 1)
return error("unrecognized argument: %s'", argv[1]);
return error("unrecognized argument: %s", argv[1]);
object_array_remove_duplicates(&revs.pending);
......
......@@ -708,6 +708,19 @@ static inline void hashclr(unsigned char *hash)
#define EMPTY_TREE_SHA1_BIN \
((const unsigned char *) EMPTY_TREE_SHA1_BIN_LITERAL)
#define EMPTY_BLOB_SHA1_HEX \
"e69de29bb2d1d6434b8b29ae775ad8c2e48c5391"
#define EMPTY_BLOB_SHA1_BIN_LITERAL \
"\xe6\x9d\xe2\x9b\xb2\xd1\xd6\x43\x4b\x8b" \
"\x29\xae\x77\x5a\xd8\xc2\xe4\x8c\x53\x91"
#define EMPTY_BLOB_SHA1_BIN \
((const unsigned char *) EMPTY_BLOB_SHA1_BIN_LITERAL)
static inline int is_empty_blob_sha1(const unsigned char *sha1)
{
return !hashcmp(sha1, EMPTY_BLOB_SHA1_BIN);
}
int git_mkstemp(char *path, size_t n, const char *template);
int git_mkstemps(char *path, size_t n, const char *template, int suffix_len);
......@@ -928,6 +941,22 @@ extern const char *fmt_name(const char *name, const char *email);
extern const char *git_editor(void);
extern const char *git_pager(int stdout_is_tty);
struct ident_split {
const char *name_begin;
const char *name_end;
const char *mail_begin;
const char *mail_end;
const char *date_begin;
const char *date_end;
const char *tz_begin;
const char *tz_end;
};
/*
* Signals an success with 0, but time part of the result may be NULL
* if the input lacks timestamp and zone
*/
extern int split_ident_line(struct ident_split *, const char *, int);
struct checkout {
const char *base_dir;
int base_dir_len;
......@@ -1276,4 +1305,6 @@ extern struct startup_info *startup_info;
/* builtin/merge.c */
int checkout_fast_forward(const unsigned char *from, const unsigned char *to);
int sane_execvp(const char *file, char *const argv[]);
#endif /* CACHE_H */
......@@ -423,7 +423,7 @@ static int make_hunks(struct sline *sline, unsigned long cnt,
hunk_begin, j);
la = (la + context < cnt + 1) ?
(la + context) : cnt + 1;
while (j <= --la) {
while (la && j <= --la) {
if (sline[la].flag & mark) {
contin = 1;
break;
......
......@@ -1003,7 +1003,7 @@ static void mingw_execve(const char *cmd, char *const *argv, char *const *env)
}
}
void mingw_execvp(const char *cmd, char *const *argv)
int mingw_execvp(const char *cmd, char *const *argv)
{
char **path = get_path_split();
char *prog = path_lookup(cmd, path, 0);
......@@ -1015,11 +1015,13 @@ void mingw_execvp(const char *cmd, char *const *argv)
errno = ENOENT;
free_path_split(path);
return -1;
}
void mingw_execv(const char *cmd, char *const *argv)
int mingw_execv(const char *cmd, char *const *argv)
{
mingw_execve(cmd, argv, environ);
return -1;
}
int mingw_kill(pid_t pid, int sig)
......
......@@ -274,9 +274,9 @@ int mingw_utime(const char *file_name, const struct utimbuf *times);
pid_t mingw_spawnvpe(const char *cmd, const char **argv, char **env,
const char *dir,
int fhin, int fhout, int fherr);
void mingw_execvp(const char *cmd, char *const *argv);
int mingw_execvp(const char *cmd, char *const *argv);
#define execvp mingw_execvp
void mingw_execv(const char *cmd, char *const *argv);
int mingw_execv(const char *cmd, char *const *argv);
#define execv mingw_execv
static inline unsigned int git_ntohl(unsigned int x)
......
......@@ -989,10 +989,74 @@ static void diff_words_flush(struct emit_callback *ecbdata)
diff_words_show(ecbdata->diff_words);
}
static void diff_filespec_load_driver(struct diff_filespec *one)
{
/* Use already-loaded driver */
if (one->driver)
return;
if (S_ISREG(one->mode))
one->driver = userdiff_find_by_path(one->path);
/* Fallback to default settings */
if (!one->driver)
one->driver = userdiff_find_by_name("default");
}
static const char *userdiff_word_regex(struct diff_filespec *one)
{
diff_filespec_load_driver(one);
return one->driver->word_regex;
}
static void init_diff_words_data(struct emit_callback *ecbdata,
struct diff_options *orig_opts,
struct diff_filespec *one,
struct diff_filespec *two)
{
int i;
struct diff_options *o = xmalloc(sizeof(struct diff_options));
memcpy(o, orig_opts, sizeof(struct diff_options));
ecbdata->diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata->diff_words->type = o->word_diff;
ecbdata->diff_words->opt = o;
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(two);
if (!o->word_regex)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
ecbdata->diff_words->word_regex = (regex_t *)
xmalloc(sizeof(regex_t));
if (regcomp(ecbdata->diff_words->word_regex,
o->word_regex,
REG_EXTENDED | REG_NEWLINE))
die ("Invalid regular expression: %s",
o->word_regex);
}
for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
if (o->word_diff == diff_words_styles[i].type) {
ecbdata->diff_words->style =
&diff_words_styles[i];
break;
}
}
if (want_color(o->use_color)) {
struct diff_words_style *st = ecbdata->diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
}
}
static void free_diff_words_data(struct emit_callback *ecbdata)
{
if (ecbdata->diff_words) {
diff_words_flush(ecbdata);
free (ecbdata->diff_words->opt);
free (ecbdata->diff_words->minus.text.ptr);
free (ecbdata->diff_words->minus.orig);
free (ecbdata->diff_words->plus.text.ptr);
......@@ -2061,20 +2125,6 @@ static void emit_binary_diff(FILE *file, mmfile_t *one, mmfile_t *two, char *pre
emit_binary_diff_body(file, two, one, prefix);
}
static void diff_filespec_load_driver(struct diff_filespec *one)
{
/* Use already-loaded driver */
if (one->driver)
return;
if (S_ISREG(one->mode))
one->driver = userdiff_find_by_path(one->path);
/* Fallback to default settings */
if (!one->driver)
one->driver = userdiff_find_by_name("default");
}
int diff_filespec_is_binary(struct diff_filespec *one)
{
if (one->is_binary == -1) {
......@@ -2100,12 +2150,6 @@ static const struct userdiff_funcname *diff_funcname_pattern(struct diff_filespe
return one->driver->funcname.pattern ? &one->driver->funcname : NULL;
}
static const char *userdiff_word_regex(struct diff_filespec *one)
{
diff_filespec_load_driver(one);
return one->driver->word_regex;
}
void diff_set_mnemonic_prefix(struct diff_options *options, const char *a, const char *b)
{
if (!options->a_prefix)
......@@ -2292,42 +2336,8 @@ static void builtin_diff(const char *name_a,
xecfg.ctxlen = strtoul(diffopts + 10, NULL, 10);
else if (!prefixcmp(diffopts, "-u"))
xecfg.ctxlen = strtoul(diffopts + 2, NULL, 10);
if (o->word_diff) {
int i;
ecbdata.diff_words =
xcalloc(1, sizeof(struct diff_words_data));
ecbdata.diff_words->type = o->word_diff;
ecbdata.diff_words->opt = o;
if (!o->word_regex)
o->word_regex = userdiff_word_regex(one);
if (!o->word_regex)
o->word_regex = userdiff_word_regex(two);
if (!o->word_regex)
o->word_regex = diff_word_regex_cfg;
if (o->word_regex) {
ecbdata.diff_words->word_regex = (regex_t *)
xmalloc(sizeof(regex_t));
if (regcomp(ecbdata.diff_words->word_regex,
o->word_regex,
REG_EXTENDED | REG_NEWLINE))
die ("Invalid regular expression: %s",
o->word_regex);
}
for (i = 0; i < ARRAY_SIZE(diff_words_styles); i++) {
if (o->word_diff == diff_words_styles[i].type) {
ecbdata.diff_words->style =
&diff_words_styles[i];
break;
}
}
if (want_color(o->use_color)) {
struct diff_words_style *st = ecbdata.diff_words->style;
st->old.color = diff_get_color_opt(o, DIFF_FILE_OLD);
st->new.color = diff_get_color_opt(o, DIFF_FILE_NEW);
st->ctx.color = diff_get_color_opt(o, DIFF_PLAIN);
}
}
if (o->word_diff)
init_diff_words_data(&ecbdata, o, one, two);
xdi_diff_outf(&mf1, &mf2, fn_out_consume, &ecbdata,
&xpp, &xecfg);
if (o->word_diff)
......@@ -3136,6 +3146,7 @@ void diff_setup(struct diff_options *options)
options->rename_limit = -1;
options->dirstat_permille = diff_dirstat_permille_default;
options->context = 3;
DIFF_OPT_SET(options, RENAME_EMPTY);
options->change = diff_change;
options->add_remove = diff_addremove;
......@@ -3506,6 +3517,10 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
}
else if (!strcmp(arg, "--no-renames"))
options->detect_rename = 0;
else if (!strcmp(arg, "--rename-empty"))
DIFF_OPT_SET(options, RENAME_EMPTY);
else if (!strcmp(arg, "--no-rename-empty"))
DIFF_OPT_CLR(options, RENAME_EMPTY);
else if (!strcmp(arg, "--relative"))
DIFF_OPT_SET(options, RELATIVE_NAME);
else if (!prefixcmp(arg, "--relative=")) {
......
......@@ -60,7 +60,7 @@ typedef struct strbuf *(*diff_prefix_fn_t)(struct diff_options *opt, void *data)
#define DIFF_OPT_SILENT_ON_REMOVE (1 << 5)
#define DIFF_OPT_FIND_COPIES_HARDER (1 << 6)
#define DIFF_OPT_FOLLOW_RENAMES (1 << 7)
/* (1 << 8) unused */
#define DIFF_OPT_RENAME_EMPTY (1 << 8)
/* (1 << 9) unused */
#define DIFF_OPT_HAS_CHANGES (1 << 10)
#define DIFF_OPT_QUICK (1 << 11)
......
......@@ -512,9 +512,15 @@ void diffcore_rename(struct diff_options *options)
else if (options->single_follow &&
strcmp(options->single_follow, p->two->path))
continue; /* not interested */
else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
is_empty_blob_sha1(p->two->sha1))
continue;
else
locate_rename_dst(p->two, 1);
}
else if (!DIFF_OPT_TST(options, RENAME_EMPTY) &&
is_empty_blob_sha1(p->one->sha1))
continue;
else if (!DIFF_PAIR_UNMERGED(p) && !DIFF_FILE_VALID(p->two)) {
/*
* If the source is a broken "delete", and
......
......@@ -1172,22 +1172,32 @@ int is_empty_dir(const char *path)
return ret;
}
int remove_dir_recursively(struct strbuf *path, int flag)
static int remove_dir_recurse(struct strbuf *path, int flag, int *kept_up)
{
DIR *dir;
struct dirent *e;
int ret = 0, original_len = path->len, len;
int ret = 0, original_len = path->len, len, kept_down = 0;
int only_empty = (flag & REMOVE_DIR_EMPTY_ONLY);
int keep_toplevel = (flag & REMOVE_DIR_KEEP_TOPLEVEL);
unsigned char submodule_head[20];
if ((flag & REMOVE_DIR_KEEP_NESTED_GIT) &&
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head))
!resolve_gitlink_ref(path->buf, "HEAD", submodule_head)) {
/* Do not descend and nuke a nested git work tree. */
if (kept_up)
*kept_up = 1;
return 0;
}
flag &= ~REMOVE_DIR_KEEP_TOPLEVEL;
dir = opendir(path->buf);
if (!dir)
return rmdir(path->buf);
if (!dir) {
/* an empty dir could be removed even if it is unreadble */
if (!keep_toplevel)
return rmdir(path->buf);
else
return -1;
}
if (path->buf[original_len - 1] != '/')
strbuf_addch(path, '/');
......@@ -1202,7 +1212,7 @@ int remove_dir_recursively(struct strbuf *path, int flag)
if (lstat(path->buf, &st))
; /* fall thru */
else if (S_ISDIR(st.st_mode)) {
if (!remove_dir_recursively(path, only_empty))
if (!remove_dir_recurse(path, flag, &kept_down))
continue; /* happy */
} else if (!only_empty && !unlink(path->buf))
continue; /* happy, too */
......@@ -1214,11 +1224,22 @@ int remove_dir_recursively(struct strbuf *path, int flag)
closedir(dir);
strbuf_setlen(path, original_len);
if (!ret)
if (!ret && !keep_toplevel && !kept_down)
ret = rmdir(path->buf);
else if (kept_up)
/*
* report the uplevel that it is not an error that we
* did not rmdir() our directory.
*/
*kept_up = !ret;
return ret;
}
int remove_dir_recursively(struct strbuf *path, int flag)
{
return remove_dir_recurse(path, flag, NULL);
}
void setup_standard_excludes(struct dir_struct *dir)
{
const char *path;
......
......@@ -102,6 +102,7 @@ extern void setup_standard_excludes(struct dir_struct *dir);
#define REMOVE_DIR_EMPTY_ONLY 01
#define REMOVE_DIR_KEEP_NESTED_GIT 02
#define REMOVE_DIR_KEEP_TOPLEVEL 04
extern int remove_dir_recursively(struct strbuf *path, int flag);
/* tries to remove the path with empty directories along it, ignores ENOENT */
......
......@@ -134,7 +134,7 @@ int execv_git_cmd(const char **argv) {
trace_argv_printf(nargv, "trace: exec:");
/* execvp() can only ever return if it fails */
execvp("git", (char **)nargv);
sane_execvp("git", (char **)nargv);
trace_printf("trace: exec failed: %s\n", strerror(errno));
......
......@@ -268,6 +268,7 @@ sub get_empty_tree {
# FILE: is file different from index?
# INDEX_ADDDEL: is it add/delete between HEAD and index?
# FILE_ADDDEL: is it add/delete between index and file?
# UNMERGED: is the path unmerged
sub list_modified {
my ($only) = @_;
......@@ -318,16 +319,10 @@ sub list_modified {
}
}
for (run_cmd_pipe(qw(git diff-files --numstat --summary --), @tracked)) {
for (run_cmd_pipe(qw(git diff-files --numstat --summary --raw --), @tracked)) {
if (($add, $del, $file) =
/^([-\d]+) ([-\d]+) (.*)/) {
$file = unquote_path($file);
if (!exists $data{$file}) {
$data{$file} = +{
INDEX => 'unchanged',
BINARY => 0,
};
}
my ($change, $bin);
if ($add eq '-' && $del eq '-') {
$change = 'binary';
......@@ -346,6 +341,18 @@ sub list_modified {
$file = unquote_path($file);
$data{$file}{FILE_ADDDEL} = $adddel;
}
elsif (/^:[0-7]+ [0-7]+ [0-9a-f]+ [0-9a-f]+ (.) (.*)$/) {
$file = unquote_path($2);
if (!exists $data{$file}) {
$data{$file} = +{
INDEX => 'unchanged',
BINARY => 0,
};
}
if ($1 eq 'U') {
$data{$file}{UNMERGED} = 1;
}
}
}
for (sort keys %data) {
......@@ -1190,6 +1197,10 @@ sub apply_patch_for_checkout_commit {
sub patch_update_cmd {
my @all_mods = list_modified($patch_mode_flavour{FILTER});
error_msg "ignoring unmerged: $_->{VALUE}\n"
for grep { $_->{UNMERGED} } @all_mods;
@all_mods = grep { !$_->{UNMERGED} } @all_mods;
my @mods = grep { !($_->{BINARY}) } @all_mods;
my @them;
......
......@@ -220,6 +220,74 @@ static int copy(char *buf, size_t size, int offset, const char *src)
return offset;
}
/*
* Reverse of fmt_ident(); given an ident line, split the fields
* to allow the caller to parse it.
* Signal a success by returning 0, but date/tz fields of the result
* can still be NULL if the input line only has the name/email part
* (e.g. reading from a reflog entry).
*/
int split_ident_line(struct ident_split *split, const char *line, int len)
{
const char *cp;
size_t span;
int status = -1;
memset(split, 0, sizeof(*split));
split->name_begin = line;
for (cp = line; *cp && cp < line + len; cp++)
if (*cp == '<') {
split->mail_begin = cp + 1;
break;
}
if (!split->mail_begin)
return status;
for (cp = split->mail_begin - 2; line < cp; cp--)
if (!isspace(*cp)) {
split->name_end = cp + 1;
break;
}
if (!split->name_end)
return status;
for (cp = split->mail_begin; cp < line + len; cp++)
if (*cp == '>') {
split->mail_end = cp;
break;
}
if (!split->mail_end)
return status;
for (cp = split->mail_end + 1; cp < line + len && isspace(*cp); cp++)
;
if (line + len <= cp)
goto person_only;
split->date_begin = cp;
span = strspn(cp, "0123456789");
if (!span)
goto person_only;
split->date_end = split->date_begin + span;
for (cp = split->date_end; cp < line + len && isspace(*cp); cp++)
;
if (line + len <= cp || (*cp != '+' && *cp != '-'))
goto person_only;
split->tz_begin = cp;
span = strspn(cp + 1, "0123456789");
if (!span)
goto person_only;
split->tz_end = split->tz_begin + 1 + span;
return 0;
person_only:
split->date_begin = NULL;
split->date_end = NULL;
split->tz_begin = NULL;
split->tz_end = NULL;
return 0;
}
static const char *env_hint =
"\n"
"*** Please tell me who you are.\n"
......
......@@ -485,6 +485,7 @@ static struct string_list *get_renames(struct merge_options *o,
renames = xcalloc(1, sizeof(struct string_list));
diff_setup(&opts);
DIFF_OPT_SET(&opts, RECURSIVE);
DIFF_OPT_CLR(&opts, RENAME_EMPTY);
opts.detect_rename = DIFF_DETECT_RENAME;
opts.rename_limit = o->merge_rename_limit >= 0 ? o->merge_rename_limit :
o->diff_rename_limit >= 0 ? o->diff_rename_limit :
......@@ -1914,7 +1915,7 @@ int merge_recursive(struct merge_options *o,
/* if there is no common ancestor, use an empty tree */
struct tree *tree;
tree = lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
tree = lookup_tree(EMPTY_TREE_SHA1_BIN);
merged_common_ancestors = make_virtual_commit(tree, "ancestor");
}
......
......@@ -267,7 +267,8 @@ static void check_notes_merge_worktree(struct notes_merge_options *o)
* Must establish NOTES_MERGE_WORKTREE.
* Abort if NOTES_MERGE_WORKTREE already exists
*/
if (file_exists(git_path(NOTES_MERGE_WORKTREE))) {
if (file_exists(git_path(NOTES_MERGE_WORKTREE)) &&
!is_empty_dir(git_path(NOTES_MERGE_WORKTREE))) {
if (advice_resolve_conflict)
die("You have not concluded your previous "
"notes merge (%s exists).\nPlease, use "
......@@ -687,51 +688,60 @@ int notes_merge_commit(struct notes_merge_options *o,
{
/*
* Iterate through files in .git/NOTES_MERGE_WORKTREE and add all
* found notes to 'partial_tree'. Write the updates notes tree to
* found notes to 'partial_tree'. Write the updated notes tree to
* the DB, and commit the resulting tree object while reusing the
* commit message and parents from 'partial_commit'.
* Finally store the new commit object SHA1 into 'result_sha1'.
*/
struct dir_struct dir;
char *path = xstrdup(git_path(NOTES_MERGE_WORKTREE "/"));
int path_len = strlen(path), i;
DIR *dir;
struct dirent *e;
struct strbuf path = STRBUF_INIT;
char *msg = strstr(partial_commit->buffer, "\n\n");
struct strbuf sb_msg = STRBUF_INIT;
int baselen;
strbuf_addstr(&path, git_path(NOTES_MERGE_WORKTREE));
if (o->verbosity >= 3)
printf("Committing notes in notes merge worktree at %.*s\n",
path_len - 1, path);
printf("Committing notes in notes merge worktree at %s\n",
path.buf);
if (!msg || msg[2] == '\0')
die("partial notes commit has empty message");
msg += 2;
memset(&dir, 0, sizeof(dir));
read_directory(&dir, path, path_len, NULL);
for (i = 0; i < dir.nr; i++) {
struct dir_entry *ent = dir.entries[i];
dir = opendir(path.buf);
if (!dir)
die_errno("could not open %s", path.buf);
strbuf_addch(&path, '/');
baselen = path.len;
while ((e = readdir(dir)) != NULL) {
struct stat st;
const char *relpath = ent->name + path_len;
unsigned char obj_sha1[20], blob_sha1[20];
if (ent->len - path_len != 40 || get_sha1_hex(relpath, obj_sha1)) {
if (is_dot_or_dotdot(e->d_name))
continue;
if (strlen(e->d_name) != 40 || get_sha1_hex(e->d_name, obj_sha1)) {
if (o->verbosity >= 3)
printf("Skipping non-SHA1 entry '%s'\n",
ent->name);
printf("Skipping non-SHA1 entry '%s%s'\n",
path.buf, e->d_name);
continue;
}
strbuf_addstr(&path, e->d_name);
/* write file as blob, and add to partial_tree */
if (stat(ent->name, &st))
die_errno("Failed to stat '%s'", ent->name);
if (index_path(blob_sha1, ent->name, &st, HASH_WRITE_OBJECT))
die("Failed to write blob object from '%s'", ent->name);
if (stat(path.buf, &st))
die_errno("Failed to stat '%s'", path.buf);
if (index_path(blob_sha1, path.buf, &st, HASH_WRITE_OBJECT))
die("Failed to write blob object from '%s'", path.buf);
if (add_note(partial_tree, obj_sha1, blob_sha1, NULL))
die("Failed to add resolved note '%s' to notes tree",
ent->name);
path.buf);
if (o->verbosity >= 4)
printf("Added resolved note for object %s: %s\n",
sha1_to_hex(obj_sha1), sha1_to_hex(blob_sha1));
strbuf_setlen(&path, baselen);
}
strbuf_attach(&sb_msg, msg, strlen(msg), strlen(msg) + 1);
......@@ -740,20 +750,25 @@ int notes_merge_commit(struct notes_merge_options *o,
if (o->verbosity >= 4)
printf("Finalized notes merge commit: %s\n",
sha1_to_hex(result_sha1));
free(path);
strbuf_release(&path);
closedir(dir);
return 0;
}
int notes_merge_abort(struct notes_merge_options *o)
{
/* Remove .git/NOTES_MERGE_WORKTREE directory and all files within */
/*
* Remove all files within .git/NOTES_MERGE_WORKTREE. We do not remove
* the .git/NOTES_MERGE_WORKTREE directory itself, since it might be
* the current working directory of the user.
*/
struct strbuf buf = STRBUF_INIT;
int ret;
strbuf_addstr(&buf, git_path(NOTES_MERGE_WORKTREE));
if (o->verbosity >= 3)
printf("Removing notes merge worktree at %s\n", buf.buf);
ret = remove_dir_recursively(&buf, 0);
printf("Removing notes merge worktree at %s/*\n", buf.buf);
ret = remove_dir_recursively(&buf, REMOVE_DIR_KEEP_TOPLEVEL);
strbuf_release(&buf);
return ret;
}
......@@ -531,41 +531,24 @@ static size_t format_person_part(struct strbuf *sb, char part,
{
/* currently all placeholders have same length */
const int placeholder_len = 2;
int start, end, tz = 0;
int tz;
unsigned long date = 0;
char *ep;
const char *name_start, *name_end, *mail_start, *mail_end, *msg_end = msg+len;
char person_name[1024];
char person_mail[1024];
struct ident_split s;
const char *name_start, *name_end, *mail_start, *mail_end;
/* advance 'end' to point to email start delimiter */
for (end = 0; end < len && msg[end] != '<'; end++)
; /* do nothing */
/*
* When end points at the '<' that we found, it should have
* matching '>' later, which means 'end' must be strictly
* below len - 1.
*/
if (end >= len - 2)
if (split_ident_line(&s, msg, len) < 0)
goto skip;
/* Seek for both name and email part */
name_start = msg;
name_end = msg+end;
while (name_end > name_start && isspace(*(name_end-1)))
name_end--;
mail_start = msg+end+1;
mail_end = mail_start;
while (mail_end < msg_end && *mail_end != '>')
mail_end++;
if (mail_end == msg_end)
goto skip;
end = mail_end-msg;
name_start = s.name_begin;
name_end = s.name_end;
mail_start = s.mail_begin;
mail_end = s.mail_end;
if (part == 'N' || part == 'E') { /* mailmap lookup */
strlcpy(person_name, name_start, name_end-name_start+1);
strlcpy(person_mail, mail_start, mail_end-mail_start+1);
strlcpy(person_name, name_start, name_end - name_start + 1);
strlcpy(person_mail, mail_start, mail_end - mail_start + 1);
mailmap_name(person_mail, sizeof(person_mail), person_name, sizeof(person_name));
name_start = person_name;
name_end = name_start + strlen(person_name);
......@@ -581,28 +564,20 @@ static size_t format_person_part(struct strbuf *sb, char part,
return placeholder_len;
}
/* advance 'start' to point to date start delimiter */
for (start = end + 1; start < len && isspace(msg[start]); start++)
; /* do nothing */
if (start >= len)
goto skip;
date = strtoul(msg + start, &ep, 10);
if (msg + start == ep)
if (!s.date_begin)
goto skip;
date = strtoul(s.date_begin, NULL, 10);
if (part == 't') { /* date, UNIX timestamp */
strbuf_add(sb, msg + start, ep - (msg + start));
strbuf_add(sb, s.date_begin, s.date_end - s.date_begin);
return placeholder_len;
}
/* parse tz */
for (start = ep - msg + 1; start < len && isspace(msg[start]); start++)
; /* do nothing */
if (start + 1 < len) {
tz = strtoul(msg + start + 1, NULL, 10);
if (msg[start] == '-')
tz = -tz;
}
tz = strtoul(s.tz_begin + 1, NULL, 10);
if (*s.tz_begin == '-')
tz = -tz;
switch (part) {
case 'd': /* date */
......@@ -621,8 +596,9 @@ static size_t format_person_part(struct strbuf *sb, char part,
skip:
/*
* bogus commit, 'sb' cannot be updated, but we still need to
* compute a valid return value.
* reading from either a bogus commit, or a reflog entry with
* %gn, %ge, etc.; 'sb' cannot be updated, but we still need
* to compute a valid return value.
*/
if (part == 'n' || part == 'e' || part == 't' || part == 'd'
|| part == 'D' || part == 'r' || part == 'i')
......
......@@ -157,16 +157,6 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
return 0;
}
static int is_empty_blob_sha1(const unsigned char *sha1)
{
static const unsigned char empty_blob_sha1[20] = {
0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
0x29,0xae,0x77,0x5a,0xd8,0xc2,0xe4,0x8c,0x53,0x91
};
return !hashcmp(sha1, empty_blob_sha1);
}
static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
{
unsigned int changed = 0;
......
......@@ -76,6 +76,68 @@ static inline void dup_devnull(int to)
}
#endif
static char *locate_in_PATH(const char *file)
{
const char *p = getenv("PATH");
struct strbuf buf = STRBUF_INIT;
if (!p || !*p)
return NULL;
while (1) {
const char *end = strchrnul(p, ':');
strbuf_reset(&buf);
/* POSIX specifies an empty entry as the current directory. */
if (end != p) {
strbuf_add(&buf, p, end - p);
strbuf_addch(&buf, '/');
}
strbuf_addstr(&buf, file);
if (!access(buf.buf, F_OK))
return strbuf_detach(&buf, NULL);
if (!*end)
break;
p = end + 1;
}
strbuf_release(&buf);
return NULL;
}
static int exists_in_PATH(const char *file)
{
char *r = locate_in_PATH(file);
free(r);
return r != NULL;
}
int sane_execvp(const char *file, char * const argv[])
{
if (!execvp(file, argv))
return 0; /* cannot happen ;-) */
/*
* When a command can't be found because one of the directories
* listed in $PATH is unsearchable, execvp reports EACCES, but
* careful usability testing (read: analysis of occasional bug
* reports) reveals that "No such file or directory" is more
* intuitive.
*
* We avoid commands with "/", because execvp will not do $PATH
* lookups in that case.
*
* The reassignment of EACCES to errno looks like a no-op below,
* but we need to protect against exists_in_PATH overwriting errno.
*/
if (errno == EACCES && !strchr(file, '/'))
errno = exists_in_PATH(file) ? EACCES : ENOENT;
return -1;
}
static const char **prepare_shell_cmd(const char **argv)
{
int argc, nargc = 0;
......@@ -114,7 +176,7 @@ static int execv_shell_cmd(const char **argv)
{
const char **nargv = prepare_shell_cmd(argv);
trace_argv_printf(nargv, "trace: exec:");
execvp(nargv[0], (char **)nargv);
sane_execvp(nargv[0], (char **)nargv);
free(nargv);
return -1;
}
......@@ -339,7 +401,7 @@ int start_command(struct child_process *cmd)
} else if (cmd->use_shell) {
execv_shell_cmd(cmd->argv);
} else {
execvp(cmd->argv[0], (char *const*) cmd->argv);
sane_execvp(cmd->argv[0], (char *const*) cmd->argv);
}
if (errno == ENOENT) {
if (!cmd->silent_exec_failure)
......
......@@ -164,7 +164,7 @@ static void write_message(struct strbuf *msgbuf, const char *filename)
static struct tree *empty_tree(void)
{
return lookup_tree((const unsigned char *)EMPTY_TREE_SHA1_BIN);
return lookup_tree(EMPTY_TREE_SHA1_BIN);
}
static int error_dirty_index(struct replay_opts *opts)
......
......@@ -13,7 +13,7 @@ enum {
void set_diffopt_flags_from_submodule_config(struct diff_options *diffopt,
const char *path);
int submodule_config(const char *var, const char *value, void *cb);
void gitmodules_config();
void gitmodules_config(void);
int parse_submodule_config_option(const char *var, const char *value);
void handle_ignore_submodules_arg(struct diff_options *diffopt, const char *);
int parse_fetch_recurse_submodules_arg(const char *opt, const char *arg);
......
......@@ -34,4 +34,17 @@ test_expect_success POSIXPERM 'run_command reports EACCES' '
grep "fatal: cannot exec.*hello.sh" err
'
test_expect_success POSIXPERM 'unreadable directory in PATH' '
mkdir local-command &&
test_when_finished "chmod u+rwx local-command && rm -fr local-command" &&
git config alias.nitfol "!echo frotz" &&
chmod a-rx local-command &&
(
PATH=./local-command:$PATH &&
git nitfol >actual
) &&
echo frotz >expect &&
test_cmp expect actual
'
test_done
......@@ -324,7 +324,7 @@ y and z notes on 4th commit
EOF
git notes merge --commit &&
# No .git/NOTES_MERGE_* files left
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# Merge commit has pre-merge y and pre-merge z as parents
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
......@@ -386,7 +386,7 @@ test_expect_success 'redo merge of z into m (== y) with default ("manual") resol
test_expect_success 'abort notes merge' '
git notes merge --abort &&
# No .git/NOTES_MERGE_* files left
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# m has not moved (still == y)
test "$(git rev-parse refs/notes/m)" = "$(cat pre_merge_y)" &&
......@@ -453,7 +453,7 @@ EOF
# Finalize merge
git notes merge --commit &&
# No .git/NOTES_MERGE_* files left
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# Merge commit has pre-merge y and pre-merge z as parents
test "$(git rev-parse refs/notes/m^1)" = "$(cat pre_merge_y)" &&
......@@ -542,7 +542,7 @@ EOF
test_expect_success 'resolve situation by aborting the notes merge' '
git notes merge --abort &&
# No .git/NOTES_MERGE_* files left
test_must_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_might_fail ls .git/NOTES_MERGE_* >output 2>/dev/null &&
test_cmp /dev/null output &&
# m has not moved (still == w)
test "$(git rev-parse refs/notes/m)" = "$(git rev-parse refs/notes/w)" &&
......@@ -553,4 +553,23 @@ test_expect_success 'resolve situation by aborting the notes merge' '
verify_notes z
'
cat >expect_notes <<EOF
foo
bar
EOF
test_expect_success 'switch cwd before committing notes merge' '
git notes add -m foo HEAD &&
git notes --ref=other add -m bar HEAD &&
test_must_fail git notes merge refs/notes/other &&
(
cd .git/NOTES_MERGE_WORKTREE &&
echo "foo" > $(git rev-parse HEAD) &&
echo "bar" >> $(git rev-parse HEAD) &&
git notes merge --commit
) &&
git notes show HEAD > actual_notes &&
test_cmp expect_notes actual_notes
'
test_done
......@@ -330,4 +330,30 @@ test_expect_success PERL 'split hunk "add -p (edit)"' '
! grep "^+15" actual
'
test_expect_success 'patch mode ignores unmerged entries' '
git reset --hard &&
test_commit conflict &&
test_commit non-conflict &&
git checkout -b side &&
test_commit side conflict.t &&
git checkout master &&
test_commit master conflict.t &&
test_must_fail git merge side &&
echo changed >non-conflict.t &&
echo y | git add -p >output &&
! grep a/conflict.t output &&
cat >expected <<-\EOF &&
* Unmerged path conflict.t
diff --git a/non-conflict.t b/non-conflict.t
index f766221..5ea2ed4 100644
--- a/non-conflict.t
+++ b/non-conflict.t
@@ -1 +1 @@
-non-conflict
+changed
EOF
git diff --cached >diff &&
test_cmp expected diff
'
test_done
......@@ -3,6 +3,7 @@
test_description='word diff colors'
. ./test-lib.sh
. "$TEST_DIRECTORY"/diff-lib.sh
cat >pre.simple <<-\EOF
h(4)
......@@ -293,6 +294,10 @@ test_expect_success '--word-diff=none' '
word_diff --word-diff=plain --word-diff=none
'
test_expect_success 'unset default driver' '
test_unconfig diff.wordregex
'
test_language_driver bibtex
test_language_driver cpp
test_language_driver csharp
......@@ -348,4 +353,35 @@ test_expect_success 'word-diff with no newline at EOF' '
word_diff --word-diff=plain
'
test_expect_success 'setup history with two files' '
echo "a b; c" >a.tex &&
echo "a b; c" >z.txt &&
git add a.tex z.txt &&
git commit -minitial &&
# modify both
echo "a bx; c" >a.tex &&
echo "a bx; c" >z.txt &&
git commit -mmodified -a
'
test_expect_success 'wordRegex for the first file does not apply to the second' '
echo "*.tex diff=tex" >.gitattributes &&
git config diff.tex.wordRegex "[a-z]+|." &&
cat >expect <<-\EOF &&
diff --git a/a.tex b/a.tex
--- a/a.tex
+++ b/a.tex
@@ -1 +1 @@
a [-b-]{+bx+}; c
diff --git a/z.txt b/z.txt
--- a/z.txt
+++ b/z.txt
@@ -1 +1 @@
a [-b;-]{+bx;+} c
EOF
git diff --word-diff HEAD~ >actual &&
compare_diff_patch expect actual
'
test_done
#!/bin/sh
test_description='check various push.default settings'
. ./test-lib.sh
test_expect_success 'setup bare remotes' '
git init --bare repo1 &&
git remote add parent1 repo1 &&
git init --bare repo2 &&
git remote add parent2 repo2 &&
test_commit one &&
git push parent1 HEAD &&
git push parent2 HEAD
'
test_expect_success '"upstream" pushes to configured upstream' '
git checkout master &&
test_config branch.master.remote parent1 &&
test_config branch.master.merge refs/heads/foo &&
test_config push.default upstream &&
test_commit two &&
git push &&
echo two >expect &&
git --git-dir=repo1 log -1 --format=%s foo >actual &&
test_cmp expect actual
'
test_expect_success '"upstream" does not push on unconfigured remote' '
git checkout master &&
test_unconfig branch.master.remote &&
test_config push.default upstream &&
test_commit three &&
test_must_fail git push
'
test_expect_success '"upstream" does not push on unconfigured branch' '
git checkout master &&
test_config branch.master.remote parent1 &&
test_unconfig branch.master.merge &&
test_config push.default upstream
test_commit four &&
test_must_fail git push
'
test_expect_success '"upstream" does not push when remotes do not match' '
git checkout master &&
test_config branch.master.remote parent1 &&
test_config branch.master.merge refs/heads/foo &&
test_config push.default upstream &&
test_commit five &&
test_must_fail git push parent2
'
test_done
......@@ -884,4 +884,20 @@ test_expect_success 'no spurious "refusing to lose untracked" message' '
! grep "refusing to lose untracked file" errors.txt
'
test_expect_success 'do not follow renames for empty files' '
git checkout -f -b empty-base &&
>empty1 &&
git add empty1 &&
git commit -m base &&
echo content >empty1 &&
git add empty1 &&
git commit -m fill &&
git checkout -b empty-topic HEAD^ &&
git mv empty1 empty2 &&
git commit -m rename &&
test_must_fail git merge empty-base &&
>expect &&
test_cmp expect empty2
'
test_done
......@@ -399,8 +399,8 @@ test_expect_success SANITY 'removal failure' '
'
test_expect_success 'nested git work tree' '
rm -fr foo bar &&
mkdir foo bar &&
rm -fr foo bar baz &&
mkdir -p foo bar baz/boo &&
(
cd foo &&
git init &&
......@@ -412,15 +412,24 @@ test_expect_success 'nested git work tree' '
cd bar &&
>goodbye.people
) &&
(
cd baz/boo &&
git init &&
>deeper.world
git add . &&
git commit -a -m deeply.nested
) &&
git clean -f -d &&
test -f foo/.git/index &&
test -f foo/hello.world &&
test -f baz/boo/.git/index &&
test -f baz/boo/deeper.world &&
! test -d bar
'
test_expect_success 'force removal of nested git work tree' '
rm -fr foo bar &&
mkdir foo bar &&
rm -fr foo bar baz &&
mkdir -p foo bar baz/boo &&
(
cd foo &&
git init &&
......@@ -432,9 +441,17 @@ test_expect_success 'force removal of nested git work tree' '
cd bar &&
>goodbye.people
) &&
(
cd baz/boo &&
git init &&
>deeper.world
git add . &&
git commit -a -m deeply.nested
) &&
git clean -f -f -d &&
! test -d foo &&
! test -d bar
! test -d bar &&
! test -d baz
'
test_expect_success 'git clean -e' '
......
......@@ -30,10 +30,12 @@ test_expect_success 'setup: initial commit' '
'
test_expect_success '-m and -F do not mix' '
git checkout HEAD file && echo >>file && git add file &&
test_must_fail git commit -m foo -m bar -F file
'
test_expect_success '-m and -C do not mix' '
git checkout HEAD file && echo >>file && git add file &&
test_must_fail git commit -C HEAD -m illegal
'
......@@ -79,7 +81,19 @@ test_expect_success 'empty commit message' '
test_must_fail git commit -F msg -a
'
test_expect_success 'template "emptyness" check does not kick in with -F' '
git checkout HEAD file && echo >>file && git add file &&
git commit -t file -F file
'
test_expect_success 'template "emptyness" check' '
git checkout HEAD file && echo >>file && git add file &&
test_must_fail git commit -t file 2>err &&
test_i18ngrep "did not edit" err
'
test_expect_success 'setup: commit message from file' '
git checkout HEAD file && echo >>file && git add file &&
echo this is the commit message, coming from a file >msg &&
git commit -F msg -a
'
......
......@@ -118,4 +118,22 @@ test_expect_success 'with failing hook requiring GIT_PREFIX' '
git checkout -- file
'
test_expect_success 'check the author in hook' '
write_script "$HOOK" <<-\EOF &&
test "$GIT_AUTHOR_NAME" = "New Author" &&
test "$GIT_AUTHOR_EMAIL" = "newauthor@example.com"
EOF
test_must_fail git commit --allow-empty -m "by a.u.thor" &&
(
GIT_AUTHOR_NAME="New Author" &&
GIT_AUTHOR_EMAIL="newauthor@example.com" &&
export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL &&
git commit --allow-empty -m "by new.author via env" &&
git show -s
) &&
git commit --author="New Author <newauthor@example.com>" \
--allow-empty -m "by new.author via command line" &&
git show -s
'
test_done
#include "cache.h"
#include "run-command.h"
int main(int argc, char **argv)
int main(int argc, const char **argv)
{
struct child_process cp;
int nogit = 0;
......@@ -9,12 +9,12 @@ int main(int argc, char **argv)
setup_git_directory_gently(&nogit);
if (nogit)
die("No git repo found");
if (!strcmp(argv[1], "--setup-work-tree")) {
if (argc > 1 && !strcmp(argv[1], "--setup-work-tree")) {
setup_work_tree();
argv++;
}
memset(&cp, 0, sizeof(cp));
cp.git_cmd = 1;
cp.argv = (const char **)argv+1;
cp.argv = argv + 1;
return run_command(&cp);
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册