--- /dev/null
+Git v2.8.1 Release Notes
+========================
+
+Fixes since v2.8
+----------------
+
+ * "make rpmbuild" target was broken as its input, git.spec.in, was
+ not updated to match a file it describes that has been renamed
+ recently. This has been fixed.
--- /dev/null
+Git v2.8.2 Release Notes
+========================
+
+Fixes since v2.8.1
+------------------
+
+ * The embedded args argv-array in the child process is used to build
+ the command line to run pack-objects instead of using a separate
+ array of strings.
+
+ * Bunch of tests on "git clone" has been renumbered for better
+ organization.
+
+ * The tests that involve running httpd leaked the system-wide
+ configuration in /etc/gitconfig to the tested environment.
+
+ * "index-pack --keep=<msg>" was broken since v2.1.0 timeframe.
+
+ * "git config --get-urlmatch", unlike other variants of the "git
+ config --get" family, did not signal error with its exit status
+ when there was no matching configuration.
+
+ * The "--local-env-vars" and "--resolve-git-dir" options of "git
+ rev-parse" failed to work outside a repository when the command's
+ option parsing was rewritten in 1.8.5 era.
+
+ * Fetching of history by naming a commit object name directly didn't
+ work across remote-curl transport.
+
+ * A small memory leak in an error codepath has been plugged in xdiff
+ code.
+
+ * strbuf_getwholeline() did not NUL-terminate the buffer on certain
+ corner cases in its error codepath.
+
+ * The startup_info data, which records if we are working inside a
+ repository (among other things), are now uniformly available to Git
+ subcommand implementations, and Git avoids attempting to touch
+ references when we are not in a repository.
+
+ * "git mergetool" did not work well with conflicts that both sides
+ deleted.
+
+ * "git send-email" had trouble parsing alias file in mailrc format
+ when lines in it had trailing whitespaces on them.
+
+ * When "git merge --squash" stopped due to conflict, the concluding
+ "git commit" failed to read in the SQUASH_MSG that shows the log
+ messages from all the squashed commits.
+
+ * "git merge FETCH_HEAD" dereferenced NULL pointer when merging
+ nothing into an unborn history (which is arguably unusual usage,
+ which perhaps was the reason why nobody noticed it).
+
+ * Build updates for MSVC.
+
+ * "git diff -M" used to work better when two originally identical
+ files A and B got renamed to X/A and X/B by pairing A to X/A and B
+ to X/B, but this was broken in the 2.0 timeframe.
+
+ * "git send-pack --all <there>" was broken when its command line
+ option parsing was written in the 2.6 timeframe.
+
+ * When running "git blame $path" with unnormalized data in the index
+ for the path, the data in the working tree was blamed, even though
+ "git add" would not have changed what is already in the index, due
+ to "safe crlf" that disables the line-end conversion. It has been
+ corrected.
+
+Also contains minor documentation updates and code clean-ups.
--- /dev/null
+Git v2.8.3 Release Notes
+========================
+
+Fixes since v2.8.2
+------------------
+
+ * "git send-email" now uses a more readable timestamps when
+ formulating a message ID.
+
+ * The repository set-up sequence has been streamlined (the biggest
+ change is that there is no longer git_config_early()), so that we
+ do not attempt to look into refs/* when we know we do not have a
+ Git repository.
+
+ * When "git worktree" feature is in use, "git branch -d" allowed
+ deletion of a branch that is checked out in another worktree
+
+ * When "git worktree" feature is in use, "git branch -m" renamed a
+ branch that is checked out in another worktree without adjusting
+ the HEAD symbolic ref for the worktree.
+
+ * "git format-patch --help" showed `-s` and `--no-patch` as if these
+ are valid options to the command. We already hide `--patch` option
+ from the documentation, because format-patch is about showing the
+ diff, and the documentation now hides these options as well.
+
+ * A change back in version 2.7 to "git branch" broke display of a
+ symbolic ref in a non-standard place in the refs/ hierarchy (we
+ expect symbolic refs to appear in refs/remotes/*/HEAD to point at
+ the primary branch the remote has, and as .git/HEAD to point at the
+ branch we locally checked out).
+
+ * A partial rewrite of "git submodule" in the 2.7 timeframe changed
+ the way the gitdir: pointer in the submodules point at the real
+ repository location to use absolute paths by accident. This has
+ been corrected.
+
+ * "git commit" misbehaved in a few minor ways when an empty message
+ is given via -m '', all of which has been corrected.
+
+ * Support for CRAM-MD5 authentication method in "git imap-send" did
+ not work well.
+
+ * The socks5:// proxy support added back in 2.6.4 days was not aware
+ that socks5h:// proxies behave differently.
+
+ * "git config" had a codepath that tried to pass a NULL to
+ printf("%s"), which nobody seems to have noticed.
+
+ * On Cygwin, object creation uses the "create a temporary and then
+ rename it to the final name" pattern, not "create a temporary,
+ hardlink it to the final name and then unlink the temporary"
+ pattern.
+
+ This is necessary to use Git on Windows shared directories, and is
+ already enabled for the MinGW and plain Windows builds. It also
+ has been used in Cygwin packaged versions of Git for quite a while.
+ See http://thread.gmane.org/gmane.comp.version-control.git/291853
+ and http://thread.gmane.org/gmane.comp.version-control.git/275680.
+
+Also contains minor documentation updates and code clean-ups.
ifdef::git-diff[]
This is the default.
endif::git-diff[]
-endif::git-format-patch[]
-s::
--no-patch::
Suppress diff output. Useful for commands like `git show` that
show the patch by default, or to cancel the effect of `--patch`.
+endif::git-format-patch[]
-U<n>::
--unified=<n>::
ifndef::git-format-patch[]
--check::
- Warn if changes introduce whitespace errors. What are
- considered whitespace errors is controlled by `core.whitespace`
+ Warn if changes introduce conflict markers or whitespace errors.
+ What are considered whitespace errors is controlled by `core.whitespace`
configuration. By default, trailing whitespaces (including
lines that solely consist of whitespaces) and a space character
that is immediately followed by a tab character inside the
[--apply] [--no-add] [--build-fake-ancestor=<file>] [-R | --reverse]
[--allow-binary-replacement | --binary] [--reject] [-z]
[-p<n>] [-C<n>] [--inaccurate-eof] [--recount] [--cached]
- [--ignore-space-change | --ignore-whitespace ]
+ [--ignore-space-change | --ignore-whitespace]
[--whitespace=(nowarn|warn|fix|error|error-all)]
[--exclude=<path>] [--include=<path>] [--directory=<root>]
[--verbose] [--unsafe-paths] [<patch>...]
DESCRIPTION
-----------
Reads the supplied diff output (i.e. "a patch") and applies it to files.
+When running from a subdirectory in a repository, patched paths
+outside the directory are ignored.
With the `--index` option the patch is also applied to the index, and
with the `--cached` option the patch is only applied to the index.
Without these options, the command applies the patch only to files,
This command will fail with non-zero status upon error. Some exit
codes are:
-. The config file is invalid (ret=3),
-. can not write to the config file (ret=4),
-. no section or name was provided (ret=2),
-. the section or key is invalid (ret=1),
-. you try to unset an option which does not exist (ret=5),
-. you try to unset/set an option for which multiple lines match (ret=5), or
-. you try to use an invalid regexp (ret=6).
+- The config file is invalid (ret=3),
+- can not write to the config file (ret=4),
+- no section or name was provided (ret=2),
+- the section or key is invalid (ret=1),
+- you try to unset an option which does not exist (ret=5),
+- you try to unset/set an option for which multiple lines match (ret=5), or
+- you try to use an invalid regexp (ret=6).
On success, the command returns the exit code 0.
found and the last value if multiple key values were found.
--get-all::
- Like get, but does not fail if the number of values for the key
- is not exactly one.
+ Like get, but returns all values for a multi-valued key.
--get-regexp::
Like --get-all, but interprets the name as a regular expression and
given URL is returned (if no such key exists, the value for
section.key is used as a fallback). When given just the
section as name, do so for all the keys in the section and
- list them.
+ list them. Returns error code 1 if no value is found.
--global::
For writing options: write to global `~/.gitconfig` file
The remote heads to update from. This is relative to
$GIT_DIR (e.g. "HEAD", "refs/heads/master"). When
unspecified, update from all heads the remote side has.
++
+If the remote has enabled the options `uploadpack.allowTipSHA1InWant` or
+`uploadpack.allowReachableSHA1InWant`, they may alternatively be 40-hex
+sha1s present on the remote.
SEE ALSO
--------
specified commit (HEAD if not specified).
--contains [<object>]::
- Only list tags which contain the specified commit (HEAD if not
+ Only list refs which contain the specified commit (HEAD if not
specified).
FIELD NAMES
branch of the `git.git` repository.
Documentation for older releases are available here:
-* link:v2.8.0/git.html[documentation for release 2.8]
+* link:v2.8.2/git.html[documentation for release 2.8.2]
* release notes for
+ link:RelNotes/2.8.2.txt[2.8.2].
+ link:RelNotes/2.8.1.txt[2.8.1].
link:RelNotes/2.8.0.txt[2.8].
* link:v2.7.3/git.html[documentation for release 2.7.3]
Specify whether include directives should be followed in parsed files.
Regular `git_config` defaults to `1`.
-There is a special version of `git_config` called `git_config_early`.
-This version takes an additional parameter to specify the repository
-config, instead of having it looked up via `git_path`. This is useful
-early in a Git program before the repository has been found. Unless
-you're working with early setup code, you probably don't want to use
-this.
-
Reading Specific Files
----------------------
pass the command-line option, which can be specified multiple times,
to another command.
+`OPT_CMDMODE(short, long, &int_var, description, enum_val)`::
+ Define an "operation mode" option, only one of which in the same
+ group of "operating mode" options that share the same `int_var`
+ can be given by the user. `enum_val` is set to `int_var` when the
+ option is used, but an error is reported if other "operating mode"
+ option has already set its value to the same `int_var`.
+
The last element of the array must be `OPT_END()`.
static void trace_print_foo(const char *message)
{
- trace_print_key(&trace_foo, message);
+ trace_printf_key(&trace_foo, "%s", message);
}
------------
+
}
trace_performance(t, "frotz");
------------
+
+Bugs & Caveats
+--------------
+
+GIT_TRACE_* environment variables can be used to tell Git to show
+trace output to its standard error stream. Git can often spawn a pager
+internally to run its subcommand and send its standard output and
+standard error to it.
+
+Because GIT_TRACE_PERFORMANCE trace is generated only at the very end
+of the program with atexit(), which happens after the pager exits, it
+would not work well if you send its log to the standard error output
+and let Git spawn the pager at the same time.
+
+As a work around, you can for example use '--no-pager', or set
+GIT_TRACE_PERFORMANCE to another file descriptor which is redirected
+to stderr, or set GIT_TRACE_PERFORMANCE to a file specified by its
+absolute path.
+
+For example instead of the following command which by default may not
+print any performance information:
+
+------------
+GIT_TRACE_PERFORMANCE=2 git log -1
+------------
+
+you may want to use:
+
+------------
+GIT_TRACE_PERFORMANCE=2 git --no-pager log -1
+------------
+
+or:
+
+------------
+GIT_TRACE_PERFORMANCE=3 3>&2 git log -1
+------------
+
+or:
+
+------------
+GIT_TRACE_PERFORMANCE=/path/to/log/file git log -1
+------------
#!/bin/sh
GVF=GIT-VERSION-FILE
-DEF_VER=v2.8.0
+DEF_VER=v2.8.2
LF='
'
#
# Define HAVE_CLOCK_MONOTONIC if your platform has CLOCK_MONOTONIC in librt.
#
-# Define NO_HMAC_CTX_CLEANUP if your OpenSSL is version 0.9.6b or earlier to
-# cleanup the HMAC context with the older HMAC_cleanup function.
-#
# Define USE_PARENS_AROUND_GETTEXT_N to "yes" if your compiler happily
# compiles the following initialization:
#
ifdef NEEDS_CRYPTO_WITH_SSL
OPENSSL_LIBSSL += -lcrypto
endif
- ifdef NO_HMAC_CTX_CLEANUP
- BASIC_CFLAGS += -DNO_HMAC_CTX_CLEANUP
- endif
else
BASIC_CFLAGS += -DNO_OPENSSL
BLK_SHA1 = 1
check: common-cmds.h
@if sparse; \
then \
- echo 2>&1 "Use 'make sparse' instead"; \
+ echo >&2 "Use 'make sparse' instead"; \
$(MAKE) --no-print-directory sparse; \
else \
- echo 2>&1 "Did you mean 'make test'?"; \
+ echo >&2 "Did you mean 'make test'?"; \
exit 1; \
fi
-Documentation/RelNotes/2.8.0.txt
\ No newline at end of file
+Documentation/RelNotes/2.8.2.txt
\ No newline at end of file
strbuf_add(&path, pfx, pfx_len);
strbuf_addstr(&path, arg);
#else
- char *p;
/* don't add prefix to absolute paths, but still replace '\' by '/' */
strbuf_reset(&path);
if (is_absolute_path(arg))
else if (pfx_len)
strbuf_add(&path, pfx, pfx_len);
strbuf_addstr(&path, arg);
- for (p = path.buf + pfx_len; *p; p++)
- if (*p == '\\')
- *p = '/';
+ convert_slashes(path.buf + pfx_len);
#endif
return path.buf;
}
const char *pattern;
int patternlen;
int nowildcardlen;
- int flags; /* EXC_FLAG_* */
+ unsigned flags; /* EXC_FLAG_* */
};
/*
die(_("'%s' is already checked out at '%s'"), branch, existing);
}
}
+
+int replace_each_worktree_head_symref(const char *oldref, const char *newref)
+{
+ int ret = 0;
+ struct worktree **worktrees = get_worktrees();
+ int i;
+
+ for (i = 0; worktrees[i]; i++) {
+ if (worktrees[i]->is_detached)
+ continue;
+ if (strcmp(oldref, worktrees[i]->head_ref))
+ continue;
+
+ if (set_worktree_head_symref(worktrees[i]->git_dir, newref)) {
+ ret = -1;
+ error(_("HEAD of working tree %s is not updated"),
+ worktrees[i]->path);
+ }
+ }
+
+ free_worktrees(worktrees);
+ return ret;
+}
*/
extern void die_if_checked_out(const char *branch);
+/*
+ * Update all per-worktree HEADs pointing at the old ref to point the new ref.
+ * This will be used when renaming a branch. Returns 0 if successful, non-zero
+ * otherwise.
+ */
+extern int replace_each_worktree_head_symref(const char *oldref, const char *newref);
+
#endif
return find_name(line, NULL, p_value, TERM_TAB);
if (orig_name) {
- int len;
- const char *name;
+ int len = strlen(orig_name);
char *another;
- name = orig_name;
- len = strlen(name);
if (isnull)
- die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"), name, linenr);
+ die(_("git apply: bad git-diff - expected /dev/null, got %s on line %d"),
+ orig_name, linenr);
another = find_name(line, NULL, p_value, TERM_TAB);
- if (!another || memcmp(another, name, len + 1))
+ if (!another || memcmp(another, orig_name, len + 1))
die((side == DIFF_NEW_NAME) ?
_("git apply: bad git-diff - inconsistent new filename on line %d") :
_("git apply: bad git-diff - inconsistent old filename on line %d"), linenr);
free(another);
return orig_name;
- }
- else {
+ } else {
/* expect "/dev/null" */
if (memcmp("/dev/null", line, 9) || line[9] != '\n')
die(_("git apply: bad git-diff - expected /dev/null on line %d"), linenr);
static int gitdiff_oldname(const char *line, struct patch *patch)
{
- char *orig = patch->old_name;
patch->old_name = gitdiff_verify_name(line, patch->is_new, patch->old_name,
DIFF_OLD_NAME);
- if (orig != patch->old_name)
- free(orig);
return 0;
}
static int gitdiff_newname(const char *line, struct patch *patch)
{
- char *orig = patch->new_name;
patch->new_name = gitdiff_verify_name(line, patch->is_delete, patch->new_name,
DIFF_NEW_NAME);
- if (orig != patch->new_name)
- free(orig);
return 0;
}
return NULL;
}
+/*
+ * Returns:
+ * -1 in case of error,
+ * the length of the parsed binary patch otherwise
+ */
static int parse_binary(char *buffer, unsigned long size, struct patch *patch)
{
/*
linenr++;
used = parse_binary(buffer + hd + llen,
size - hd - llen, patch);
+ if (used < 0)
+ return -1;
if (used)
patchsize = used + llen;
else
patch->inaccurate_eof = !!(options & INACCURATE_EOF);
patch->recount = !!(options & RECOUNT);
nr = parse_chunk(buf.buf + offset, buf.len - offset, patch);
- if (nr < 0)
+ if (nr < 0) {
+ free_patch(patch);
break;
+ }
if (apply_in_reverse)
reverse_patches(patch);
if (use_patch(patch)) {
unsigned mode;
struct strbuf msg = STRBUF_INIT;
+ read_cache();
time(&now);
commit = alloc_commit_node();
commit->object.parsed = 1;
#include "utf8.h"
#include "wt-status.h"
#include "ref-filter.h"
+#include "worktree.h"
static const char * const builtin_branch_usage[] = {
N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"),
int flags = 0;
strbuf_branchname(&bname, argv[i]);
- if (kinds == FILTER_REFS_BRANCHES && !strcmp(head, bname.buf)) {
- error(_("Cannot delete the branch '%s' "
- "which you are currently on."), bname.buf);
- ret = 1;
- continue;
- }
-
free(name);
-
name = mkpathdup(fmt, bname.buf);
+
+ if (kinds == FILTER_REFS_BRANCHES) {
+ char *worktree = find_shared_symref("HEAD", name);
+ if (worktree) {
+ error(_("Cannot delete branch '%s' "
+ "checked out at '%s'"),
+ bname.buf, worktree);
+ free(worktree);
+ ret = 1;
+ continue;
+ }
+ }
+
target = resolve_ref_unsafe(name,
RESOLVE_REF_READING
| RESOLVE_REF_NO_RECURSE
int current = 0;
int color;
struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
- const char *prefix = "";
+ const char *prefix_to_show = "";
+ const char *prefix_to_skip = NULL;
const char *desc = item->refname;
char *to_free = NULL;
switch (item->kind) {
case FILTER_REFS_BRANCHES:
- skip_prefix(desc, "refs/heads/", &desc);
+ prefix_to_skip = "refs/heads/";
+ skip_prefix(desc, prefix_to_skip, &desc);
if (!filter->detached && !strcmp(desc, head))
current = 1;
else
color = BRANCH_COLOR_LOCAL;
break;
case FILTER_REFS_REMOTES:
- skip_prefix(desc, "refs/remotes/", &desc);
+ prefix_to_skip = "refs/remotes/";
+ skip_prefix(desc, prefix_to_skip, &desc);
color = BRANCH_COLOR_REMOTE;
- prefix = remote_prefix;
+ prefix_to_show = remote_prefix;
break;
case FILTER_REFS_DETACHED_HEAD:
desc = to_free = get_head_description();
color = BRANCH_COLOR_CURRENT;
}
- strbuf_addf(&name, "%s%s", prefix, desc);
+ strbuf_addf(&name, "%s%s", prefix_to_show, desc);
if (filter->verbose) {
int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
name.buf, branch_get_color(BRANCH_COLOR_RESET));
if (item->symref) {
- skip_prefix(item->symref, "refs/remotes/", &desc);
- strbuf_addf(&out, " -> %s", desc);
+ const char *symref = item->symref;
+ if (prefix_to_skip)
+ skip_prefix(symref, prefix_to_skip, &symref);
+ strbuf_addf(&out, " -> %s", symref);
}
else if (filter->verbose)
/* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
if (recovery)
warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
- /* no need to pass logmsg here as HEAD didn't really move */
- if (!strcmp(oldname, head) && create_symref("HEAD", newref.buf, NULL))
+ if (replace_each_worktree_head_symref(oldref.buf, newref.buf))
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
}
}
- if (message.len) {
+ if (have_option_m) {
strbuf_addbuf(&sb, &message);
hook_arg1 = "message";
} else if (logfile && !strcmp(logfile, "-")) {
&sb, &ctx);
hook_arg1 = "message";
} else if (!stat(git_path_merge_msg(), &statbuf)) {
+ /*
+ * prepend SQUASH_MSG here if it exists and a
+ * "merge --squash" was originally performed
+ */
+ if (!stat(git_path_squash_msg(), &statbuf)) {
+ if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
+ die_errno(_("could not read SQUASH_MSG"));
+ hook_arg1 = "squash";
+ } else
+ hook_arg1 = "merge";
if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0)
die_errno(_("could not read MERGE_MSG"));
- hook_arg1 = "merge";
} else if (!stat(git_path_squash_msg(), &statbuf)) {
if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
die_errno(_("could not read SQUASH_MSG"));
f++;
if (f > 1)
die(_("Only one of -c/-C/-F/--fixup can be used."));
- if (message.len && f > 0)
+ if (have_option_m && f > 0)
die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
- if (f || message.len)
+ if (f || have_option_m)
template_file = NULL;
if (edit_message)
use_message = edit_message;
static int get_urlmatch(const char *var, const char *url)
{
+ int ret;
char *section_tail;
struct string_list_item *item;
struct urlmatch_config config = { STRING_LIST_INIT_DUP };
git_config_with_options(urlmatch_config_entry, &config,
&given_config_source, respect_includes);
+ ret = !values.nr;
+
for_each_string_list_item(item, &values) {
struct urlmatch_current_candidate_value *matched = item->util;
struct strbuf buf = STRBUF_INIT;
free(config.url.url);
free((void *)config.section);
- return 0;
+ return ret;
}
static char *default_user_config(void)
struct ref *ref;
struct object_id oid;
- if (!get_oid_hex(name, &oid) && name[GIT_SHA1_HEXSZ] == ' ')
- name += GIT_SHA1_HEXSZ + 1;
- else
+ if (!get_oid_hex(name, &oid)) {
+ if (name[GIT_SHA1_HEXSZ] == ' ') {
+ /* <sha1> <ref>, find refname */
+ name += GIT_SHA1_HEXSZ + 1;
+ } else if (name[GIT_SHA1_HEXSZ] == '\0') {
+ ; /* <sha1>, leave sha1 as name */
+ } else {
+ /* <ref>, clear cruft from oid */
+ oidclr(&oid);
+ }
+ } else {
+ /* <ref>, clear cruft from get_oid_hex */
oidclr(&oid);
+ }
ref = alloc_ref(name);
oidcpy(&ref->old_oid, &oid);
}
static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec,
- int exc_std)
+ int exc_std, int use_index)
{
struct dir_struct dir;
int i, hit = 0;
memset(&dir, 0, sizeof(dir));
+ if (!use_index)
+ dir.flags |= DIR_NO_GITLINKS;
if (exc_std)
setup_standard_excludes(&dir);
int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
if (list.nr)
die(_("--no-index or --untracked cannot be used with revs."));
- hit = grep_directory(&opt, &pathspec, use_exclude);
+ hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
} else if (0 <= opt_exclude) {
die(_("--[no-]exclude-standard cannot be used for tracked contents."));
} else if (!list.nr) {
}
}
+static const char *derive_filename(const char *pack_name, const char *suffix,
+ struct strbuf *buf)
+{
+ size_t len;
+ if (!strip_suffix(pack_name, ".pack", &len))
+ die(_("packfile name '%s' does not end with '.pack'"),
+ pack_name);
+ strbuf_add(buf, pack_name, len);
+ strbuf_addstr(buf, suffix);
+ return buf->buf;
+}
+
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
usage(index_pack_usage);
if (fix_thin_pack && !from_stdin)
die(_("--fix-thin cannot be used without --stdin"));
- if (!index_name && pack_name) {
- size_t len;
- if (!strip_suffix(pack_name, ".pack", &len))
- die(_("packfile name '%s' does not end with '.pack'"),
- pack_name);
- strbuf_add(&index_name_buf, pack_name, len);
- strbuf_addstr(&index_name_buf, ".idx");
- index_name = index_name_buf.buf;
- }
- if (keep_msg && !keep_name && pack_name) {
- size_t len;
- if (!strip_suffix(pack_name, ".pack", &len))
- die(_("packfile name '%s' does not end with '.pack'"),
- pack_name);
- strbuf_add(&keep_name_buf, pack_name, len);
- strbuf_addstr(&keep_name_buf, ".idx");
- keep_name = keep_name_buf.buf;
- }
+ if (!index_name && pack_name)
+ index_name = derive_filename(pack_name, ".idx", &index_name_buf);
+ if (keep_msg && !keep_name && pack_name)
+ keep_name = derive_filename(pack_name, ".keep", &keep_name_buf);
+
if (verify) {
if (!index_name)
die(_("--verify with no packfile name given"));
struct strbuf path = STRBUF_INIT;
struct strbuf template_path = STRBUF_INIT;
size_t template_len;
+ struct repository_format template_format;
+ struct strbuf err = STRBUF_INIT;
DIR *dir;
char *to_free = NULL;
/* Make sure that template is from the correct vintage */
strbuf_addstr(&template_path, "config");
- repository_format_version = 0;
- git_config_from_file(check_repository_format_version,
- template_path.buf, NULL);
+ read_repository_format(&template_format, template_path.buf);
strbuf_setlen(&template_path, template_len);
- if (repository_format_version &&
- repository_format_version != GIT_REPO_VERSION) {
- warning(_("not copying templates of "
- "a wrong format version %d from '%s'"),
- repository_format_version,
- template_dir);
+ /*
+ * No mention of version at all is OK, but anything else should be
+ * verified.
+ */
+ if (template_format.version >= 0 &&
+ verify_repository_format(&template_format, &err) < 0) {
+ warning(_("not copying templates from '%s': %s"),
+ template_dir, err.buf);
+ strbuf_release(&err);
goto close_free_return;
}
/* reading existing config may have overwrote it */
if (init_shared_repository != -1)
- shared_repository = init_shared_repository;
+ set_shared_repository(init_shared_repository);
/*
* We would have created the above under user's umask -- under
* shared-repository settings, we would need to fix them up.
*/
- if (shared_repository) {
+ if (get_shared_repository()) {
adjust_shared_perm(get_git_dir());
adjust_shared_perm(git_path_buf(&buf, "refs"));
adjust_shared_perm(git_path_buf(&buf, "refs/heads"));
set_git_dir(real_path(git_dir));
git_link = NULL;
}
+ startup_info->have_repository = 1;
return 0;
}
create_object_directory();
- if (shared_repository) {
+ if (get_shared_repository()) {
char buf[10];
/* We do not spell "group" and such, so that
* the configuration can be read by older version
* and compatibility values for PERM_GROUP and
* PERM_EVERYBODY.
*/
- if (shared_repository < 0)
+ if (get_shared_repository() < 0)
/* force to the mode value */
- xsnprintf(buf, sizeof(buf), "0%o", -shared_repository);
- else if (shared_repository == PERM_GROUP)
+ xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository());
+ else if (get_shared_repository() == PERM_GROUP)
xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP);
- else if (shared_repository == PERM_EVERYBODY)
+ else if (get_shared_repository() == PERM_EVERYBODY)
xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY);
else
die("BUG: invalid value for shared_repository");
"", and the last '%s%s' is the verbatim directory name. */
printf(_("%s%s Git repository in %s%s\n"),
reinit ? _("Reinitialized existing") : _("Initialized empty"),
- shared_repository ? _(" shared") : "",
+ get_shared_repository() ? _(" shared") : "",
git_dir, len && git_dir[len-1] != '/' ? "/" : "");
}
* and we know shared_repository should always be 0;
* but just in case we play safe.
*/
- saved = shared_repository;
- shared_repository = 0;
+ saved = get_shared_repository();
+ set_shared_repository(0);
switch (safe_create_leading_directories_const(argv[0])) {
case SCLD_OK:
case SCLD_PERMS:
die_errno(_("cannot mkdir %s"), argv[0]);
break;
}
- shared_repository = saved;
+ set_shared_repository(saved);
if (mkdir(argv[0], 0777) < 0)
die_errno(_("cannot mkdir %s"), argv[0]);
mkdir_tried = 1;
}
if (init_shared_repository != -1)
- shared_repository = init_shared_repository;
+ set_shared_repository(init_shared_repository);
/*
* GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
builtin_merge_options);
if (!head_commit) {
- struct commit *remote_head;
/*
* If the merged head is a valid one there is no reason
* to forbid "git merge" into a branch yet to be born.
* We do the same for "git pull".
*/
+ unsigned char *remote_head_sha1;
if (squash)
die(_("Squash commit into empty head not supported yet"));
if (fast_forward == FF_NO)
"an empty head"));
remoteheads = collect_parents(head_commit, &head_subsumed,
argc, argv, NULL);
- remote_head = remoteheads->item;
- if (!remote_head)
+ if (!remoteheads)
die(_("%s - not something we can merge"), argv[0]);
if (remoteheads->next)
die(_("Can merge only exactly one commit into empty head"));
- read_empty(remote_head->object.oid.hash, 0);
- update_ref("initial pull", "HEAD", remote_head->object.oid.hash,
+ remote_head_sha1 = remoteheads->item->object.oid.hash;
+ read_empty(remote_head_sha1, 0);
+ update_ref("initial pull", "HEAD", remote_head_sha1,
NULL, 0, UPDATE_REFS_DIE_ON_ERR);
goto done;
}
};
check_replace_refs = 0;
+ git_config(git_default_config, NULL);
argc = parse_options(argc, argv, prefix, options, git_replace_usage, 0);
int cmd_rev_parse(int argc, const char **argv, const char *prefix)
{
int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0;
+ int did_repo_setup = 0;
int has_dashdash = 0;
int output_prefix = 0;
unsigned char sha1[20];
}
}
- prefix = setup_git_directory();
- git_config(git_default_config, NULL);
+ /* No options; just report on whether we're in a git repo or not. */
+ if (argc == 1) {
+ setup_git_directory();
+ git_config(git_default_config, NULL);
+ return 0;
+ }
+
for (i = 1; i < argc; i++) {
const char *arg = argv[i];
+ if (!strcmp(arg, "--local-env-vars")) {
+ int i;
+ for (i = 0; local_repo_env[i]; i++)
+ printf("%s\n", local_repo_env[i]);
+ continue;
+ }
+ if (!strcmp(arg, "--resolve-git-dir")) {
+ const char *gitdir = argv[++i];
+ if (!gitdir)
+ die("--resolve-git-dir requires an argument");
+ gitdir = resolve_gitdir(gitdir);
+ if (!gitdir)
+ die("not a gitdir '%s'", argv[i]);
+ puts(gitdir);
+ continue;
+ }
+
+ /* The rest of the options require a git repository. */
+ if (!did_repo_setup) {
+ prefix = setup_git_directory();
+ git_config(git_default_config, NULL);
+ did_repo_setup = 1;
+ }
+
if (!strcmp(arg, "--git-path")) {
if (!argv[i + 1])
die("--git-path requires an argument");
add_ref_exclusion(&ref_excludes, arg + 10);
continue;
}
- if (!strcmp(arg, "--local-env-vars")) {
- int i;
- for (i = 0; local_repo_env[i]; i++)
- printf("%s\n", local_repo_env[i]);
- continue;
- }
if (!strcmp(arg, "--show-toplevel")) {
const char *work_tree = get_git_work_tree();
if (work_tree)
puts(prefix_filename(pfx, strlen(pfx), get_git_common_dir()));
continue;
}
- if (!strcmp(arg, "--resolve-git-dir")) {
- const char *gitdir = argv[++i];
- if (!gitdir)
- die("--resolve-git-dir requires an argument");
- gitdir = resolve_gitdir(gitdir);
- if (!gitdir)
- die("not a gitdir '%s'", argv[i]);
- puts(gitdir);
- continue;
- }
if (!strcmp(arg, "--is-inside-git-dir")) {
printf("%s\n", is_inside_git_dir() ? "true"
: "false");
* --all and --mirror are incompatible; neither makes sense
* with any refspecs.
*/
- if ((refspecs && (send_all || args.send_mirror)) ||
+ if ((nr_refspecs > 0 && (send_all || args.send_mirror)) ||
(send_all && args.send_mirror))
usage_with_options(send_pack_usage, options);
static int module_clone(int argc, const char **argv, const char *prefix)
{
- const char *path = NULL, *name = NULL, *url = NULL;
+ const char *name = NULL, *url = NULL;
const char *reference = NULL, *depth = NULL;
int quiet = 0;
FILE *submodule_dot_git;
- char *sm_gitdir, *cwd, *p;
+ char *p, *path = NULL, *sm_gitdir;
struct strbuf rel_path = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT;
argc = parse_options(argc, argv, prefix, module_clone_options,
git_submodule_helper_usage, 0);
+ if (!path || !*path)
+ die(_("submodule--helper: unspecified or empty --path"));
+
strbuf_addf(&sb, "%s/modules/%s", get_git_dir(), name);
- sm_gitdir = strbuf_detach(&sb, NULL);
+ sm_gitdir = xstrdup(absolute_path(sb.buf));
+ strbuf_reset(&sb);
+
+ if (!is_absolute_path(path)) {
+ strbuf_addf(&sb, "%s/%s", get_git_work_tree(), path);
+ path = strbuf_detach(&sb, NULL);
+ } else
+ path = xstrdup(path);
if (!file_exists(sm_gitdir)) {
if (safe_create_leading_directories_const(sm_gitdir) < 0)
}
/* Write a .git file in the submodule to redirect to the superproject. */
- if (safe_create_leading_directories_const(path) < 0)
- die(_("could not create directory '%s'"), path);
-
- if (path && *path)
- strbuf_addf(&sb, "%s/.git", path);
- else
- strbuf_addstr(&sb, ".git");
-
+ strbuf_addf(&sb, "%s/.git", path);
if (safe_create_leading_directories_const(sb.buf) < 0)
die(_("could not create leading directories of '%s'"), sb.buf);
submodule_dot_git = fopen(sb.buf, "w");
if (!submodule_dot_git)
die_errno(_("cannot open file '%s'"), sb.buf);
- fprintf(submodule_dot_git, "gitdir: %s\n",
- relative_path(sm_gitdir, path, &rel_path));
+ fprintf_or_die(submodule_dot_git, "gitdir: %s\n",
+ relative_path(sm_gitdir, path, &rel_path));
if (fclose(submodule_dot_git))
die(_("could not close file %s"), sb.buf);
strbuf_reset(&sb);
strbuf_reset(&rel_path);
- cwd = xgetcwd();
/* Redirect the worktree of the submodule in the superproject's config */
- if (!is_absolute_path(sm_gitdir)) {
- strbuf_addf(&sb, "%s/%s", cwd, sm_gitdir);
- free(sm_gitdir);
- sm_gitdir = strbuf_detach(&sb, NULL);
- }
-
- strbuf_addf(&sb, "%s/%s", cwd, path);
p = git_pathdup_submodule(path, "config");
if (!p)
die(_("could not get submodule directory for '%s'"), path);
git_config_set_in_file(p, "core.worktree",
- relative_path(sb.buf, sm_gitdir, &rel_path));
+ relative_path(path, sm_gitdir, &rel_path));
strbuf_release(&sb);
strbuf_release(&rel_path);
free(sm_gitdir);
- free(cwd);
+ free(path);
free(p);
return 0;
}
extern int log_all_ref_updates;
extern int warn_ambiguous_refs;
extern int warn_on_object_refname_ambiguity;
-extern int shared_repository;
extern const char *apply_default_whitespace;
extern const char *apply_default_ignorewhitespace;
extern const char *git_attributes_file;
extern unsigned long big_file_threshold;
extern unsigned long pack_size_limit_cfg;
+void set_shared_repository(int value);
+int get_shared_repository(void);
+
/*
* Do replace refs need to be checked this run? This variable is
* initialized to true unless --no-replace-object is used or
*/
#define GIT_REPO_VERSION 0
#define GIT_REPO_VERSION_READ 1
-extern int repository_format_version;
extern int repository_format_precious_objects;
-extern int check_repository_format(void);
+
+struct repository_format {
+ int version;
+ int precious_objects;
+ int is_bare;
+ char *work_tree;
+ struct string_list unknown_extensions;
+};
+
+/*
+ * Read the repository format characteristics from the config file "path" into
+ * "format" struct. Returns the numeric version. On error, -1 is returned,
+ * format->version is set to -1, and all other fields in the struct are
+ * undefined.
+ */
+int read_repository_format(struct repository_format *format, const char *path);
+
+/*
+ * Verify that the repository described by repository_format is something we
+ * can read. If it is, return 0. Otherwise, return -1, and "err" will describe
+ * any errors encountered.
+ */
+int verify_repository_format(const struct repository_format *format,
+ struct strbuf *err);
+
+/*
+ * Check the repository format version in the path found in get_git_dir(),
+ * and die if it is a version we don't understand. Generally one would
+ * set_git_dir() before calling this, and use it only for "are we in a valid
+ * repo?".
+ */
+extern void check_repository_format(void);
#define MTIME_CHANGED 0x0001
#define CTIME_CHANGED 0x0002
extern int git_config_with_options(config_fn_t fn, void *,
struct git_config_source *config_source,
int respect_includes);
-extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
extern int git_parse_ulong(const char *, unsigned long *);
extern int git_parse_maybe_bool(const char *);
extern int git_config_int(const char *, const char *);
extern int git_config_rename_section(const char *, const char *);
extern int git_config_rename_section_in_file(const char *, const char *, const char *);
extern const char *git_etc_gitconfig(void);
-extern int check_repository_format_version(const char *var, const char *value, void *cb);
extern int git_env_bool(const char *, int);
extern unsigned long git_env_ulong(const char *, unsigned long);
extern int git_config_system(void);
/* Takes a negative value returned by split_cmdline */
const char *split_cmdline_strerror(int cmdline_errno);
-/* git.c */
+/* setup.c */
struct startup_info {
int have_repository;
const char *prefix;
#define HEADER_HMAC_H
#define HEADER_SHA_H
#include <CommonCrypto/CommonHMAC.h>
-#define HMAC_CTX CCHmacContext
-#define HMAC_Init(hmac, key, len, algo) CCHmacInit(hmac, algo, key, len)
-#define HMAC_Update CCHmacUpdate
-#define HMAC_Final(hmac, hash, ptr) CCHmacFinal(hmac, hash)
-#define HMAC_CTX_cleanup(ignore)
#define EVP_md5(...) kCCHmacAlgMD5
+/* CCHmac doesn't take md_len and the return type is void */
+#define HMAC git_CC_HMAC
+static inline unsigned char *git_CC_HMAC(CCHmacAlgorithm alg,
+ const void *key, int key_len,
+ const unsigned char *data, size_t data_len,
+ unsigned char *md, unsigned int *md_len)
+{
+ CCHmac(alg, key, key_len, data, data_len, md);
+ return md;
+}
+
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1070
#define APPLE_LION_OR_NEWER
#include <Security/Security.h>
char *mingw_getcwd(char *pointer, int len)
{
- int i;
wchar_t wpointer[MAX_PATH];
if (!_wgetcwd(wpointer, ARRAY_SIZE(wpointer)))
return NULL;
if (xwcstoutf(pointer, wpointer, len) < 0)
return NULL;
- for (i = 0; pointer[i]; i++)
- if (pointer[i] == '\\')
- pointer[i] = '/';
+ convert_slashes(pointer);
return pointer;
}
* executable (by not mistaking the dir separators
* for escape characters).
*/
- for (; *tmp; tmp++)
- if (*tmp == '\\')
- *tmp = '/';
+ convert_slashes(tmp);
}
/* simulate TERM to enable auto-color (see color.c) */
int mingw_offset_1st_component(const char *path);
#define offset_1st_component mingw_offset_1st_component
#define PATH_SEP ';'
-#ifndef __MINGW64_VERSION_MAJOR
+#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
#define PRIuMAX "I64u"
#define PRId64 "I64d"
#else
* always have room for a trailing NUL byte.
*/
#ifndef SNPRINTF_SIZE_CORR
-#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4)
+#if defined(WIN32) && (!defined(__GNUC__) || __GNUC__ < 4) && (!defined(_MSC_VER) || _MSC_VER < 1900)
#define SNPRINTF_SIZE_CORR 1
#else
#define SNPRINTF_SIZE_CORR 0
typedef int64_t off64_t;
+#if !defined(_MSC_VER) || _MSC_VER < 1600
#define INTMAX_MIN _I64_MIN
#define INTMAX_MAX _I64_MAX
#define UINTMAX_MAX _UI64_MAX
#define UINT32_MAX 0xffffffff /* 4294967295U */
+#else
+#include <stdint.h>
+#endif
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
expanded = expand_user_path(path);
if (!expanded)
- return error("Could not expand include path '%s'", path);
+ return error("could not expand include path '%s'", path);
path = expanded;
/*
else if (!strcmp(value, "always"))
autorebase = AUTOREBASE_ALWAYS;
else
- return error("Malformed value for %s", var);
+ return error("malformed value for %s", var);
return 0;
}
else if (!strcmp(value, "current"))
push_default = PUSH_DEFAULT_CURRENT;
else {
- error("Malformed value for %s: %s", var, value);
+ error("malformed value for %s: %s", var, value);
return error("Must be one of nothing, matching, simple, "
"upstream or current.");
}
return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
}
-int git_config_early(config_fn_t fn, void *data, const char *repo_config)
+static int do_git_config_sequence(config_fn_t fn, void *data)
{
int ret = 0, found = 0;
char *xdg_config = xdg_config_home("config");
char *user_config = expand_user_path("~/.gitconfig");
+ char *repo_config = git_pathdup("config");
if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0)) {
ret += git_config_from_file(fn, git_etc_gitconfig(),
free(xdg_config);
free(user_config);
+ free(repo_config);
return ret == 0 ? found : ret;
}
struct git_config_source *config_source,
int respect_includes)
{
- char *repo_config = NULL;
- int ret;
struct config_include_data inc = CONFIG_INCLUDE_INIT;
if (respect_includes) {
else if (config_source && config_source->blob)
return git_config_from_blob_ref(fn, config_source->blob, data);
- repo_config = git_pathdup("config");
- ret = git_config_early(fn, data, repo_config);
- if (repo_config)
- free(repo_config);
- return ret;
+ return do_git_config_sequence(fn, data);
}
static void git_config_raw(config_fn_t fn, void *data)
const char *key, const char *value,
const char *value_regex, int multi_replace)
{
- if (git_config_set_multivar_in_file_gently(config_filename, key, value,
- value_regex, multi_replace) < 0)
- die(_("Could not set '%s' to '%s'"), key, value);
+ if (!git_config_set_multivar_in_file_gently(config_filename, key, value,
+ value_regex, multi_replace))
+ return;
+ if (value)
+ die(_("could not set '%s' to '%s'"), key, value);
+ else
+ die(_("could not unset '%s'"), key);
}
int git_config_set_multivar_gently(const char *key, const char *value,
#undef config_error_nonbool
int config_error_nonbool(const char *var)
{
- return error("Missing value for '%s'", var);
+ return error("missing value for '%s'", var);
}
int parse_config_key(const char *var,
X = .exe
UNRELIABLE_FSTAT = UnfortunatelyYes
SPARSE_FLAGS = -isystem /usr/include/w32api -Wno-one-bit-signed-bitfield
+ OBJECT_CREATION_USES_RENAMES = UnfortunatelyNeedsTo
endif
ifeq ($(uname_S),FreeBSD)
NEEDS_LIBICONV = YesPlease
[CHARSET_LIB=-lcharset])])
GIT_CONF_SUBST([CHARSET_LIB])
#
-# Define NO_HMAC_CTX_CLEANUP=YesPlease if HMAC_CTX_cleanup is missing.
-AC_CHECK_LIB([crypto], [HMAC_CTX_cleanup],
- [], [GIT_CONF_SUBST([NO_HMAC_CTX_CLEANUP], [YesPlease])])
-#
# Define HAVE_CLOCK_GETTIME=YesPlease if clock_gettime is available.
GIT_CHECK_FUNC(clock_gettime,
[HAVE_CLOCK_GETTIME=YesPlease],
+Release 1.3.0
+=============
+
+* New options multimailhook.htmlInIntro and multimailhook.htmlInFooter
+ now allow using HTML in the introduction and footer of emails (e.g.
+ for a more pleasant formatting or to insert a link to the commit on
+ a web interface).
+
+* A new option multimailhook.commitBrowseURL gives a simpler (and less
+ flexible) way to add a link to a web interface for commit emails
+ than multimailhook.htmlInIntro and multimailhook.htmlInFooter.
+
+* A new public function config.add_config_parameters was added to
+ allow custom hooks to set specific Git configuration variables
+ without modifying the configuration files. See an example in
+ post-receive.example.
+
+* Error handling for SMTP has been improved (we used to print Python
+ backtraces for legitimate errors).
+
+* The SMTP mailer can now check TLS certificates when the newly added
+ configuration variable multimailhook.smtpCACerts.
+
+* Python 3 portability has been improved.
+
+* The documentation's formatting has been improved.
+
+* The testsuite has been improved (we now use pyflakes to check for
+ errors in the code).
+
+This version has been tested with Python 2.4 and 2.6 to 3.5, and Git
+v1.7.10-406-gdc801e7, 2.1.4 and 2.8.1.339.g3ad15fd.
+
+No change since 1.3 RC1.
+
Release 1.2.0
=============
+Contributing
+============
+
git-multimail is an open-source project, built by volunteers. We would
welcome your help!
Please note that although a copy of git-multimail is distributed in
the "contrib" section of the main Git project, development takes place
-in a separate git-multimail repository on GitHub:
-
- https://github.com/git-multimail/git-multimail
+in a separate `git-multimail repository on GitHub`_.
Whenever enough changes to git-multimail have accumulated, a new
code-drop of git-multimail will be submitted for inclusion in the Git
project practice
<https://github.com/git/git/blob/master/Documentation/SubmittingPatches#L234>`__.
-General discussion of git-multimail can take place on the main Git
-mailing list,
-
- git@vger.kernel.org
+General discussion of git-multimail can take place on the main `Git
+mailing list`_.
Please CC emails regarding git-multimail to the maintainers so that we
don't overlook them.
+
+
+.. _`git-multimail repository on GitHub`: https://github.com/git-multimail/git-multimail
+.. _`Git mailing list`: git@vger.kernel.org
-git-multimail (version 1.2.0)
-=============================
+git-multimail 1.3.0
+===================
.. image:: https://travis-ci.org/git-multimail/git-multimail.svg?branch=master
:target: https://travis-ci.org/git-multimail/git-multimail
community.)
+Troubleshooting/FAQ
+-------------------
+
+Please read `<doc/troubleshooting.rst>`__ for frequently asked
+questions and common issues with git-multimail.
+
+
Configuration
-------------
following ``git config`` settings:
multimailhook.environment
-
This describes the general environment of the repository. In most
cases, you do not need to specify a value for this variable:
`git-multimail` will autodetect which environment to use.
Currently supported values:
- * generic
-
+ generic
the username of the pusher is read from $USER or $USERNAME and
the repository name is derived from the repository's path.
- * gitolite
-
+ gitolite
the username of the pusher is read from $GL_USER, the repository
name is read from $GL_REPO, and the From: header value is
optionally read from gitolite.conf (see multimailhook.from).
For more information about gitolite and git-multimail, read
`<doc/gitolite.rst>`__
- * stash
-
+ stash
Environment to use when ``git-multimail`` is ran as an Atlassian
BitBucket Server (formerly known as Atlassian Stash) hook.
and repo come from these two command line flags, which must be
specified.
- * gerrit
-
+ gerrit
Environment to use when ``git-multimail`` is ran as a
``ref-updated`` Gerrit hook.
* If none of the above apply, then ``generic`` is used.
multimailhook.repoName
-
A short name of this Git repository, to be used in various places
in the notification email text. The default is to use $GL_REPO
for gitolite repositories, or otherwise to derive this value from
the repository path name.
multimailhook.mailingList
-
The list of email addresses to which notification emails should be
sent, as RFC 2822 email addresses separated by commas. This
configuration option can be multivalued. Leave it unset or set it
specific types of notification email.
multimailhook.refchangeList
-
The list of email addresses to which summary emails about
reference changes should be sent, as RFC 2822 email addresses
separated by commas. This configuration option can be
multimailhook.mailingList is set.
multimailhook.announceList
-
The list of email addresses to which emails about new annotated
tags should be sent, as RFC 2822 email addresses separated by
commas. This configuration option can be multivalued. The
even if one of the other values is set.
multimailhook.commitList
-
The list of email addresses to which emails about individual new
commits should be sent, as RFC 2822 email addresses separated by
commas. This configuration option can be multivalued. The
multimailhook.mailingList is set.
multimailhook.announceShortlog
-
If this option is set to true, then emails about changes to
annotated tags include a shortlog of changes since the previous
tag. This can be useful if the annotated tags represent releases;
rather than useful. Default is false.
multimailhook.commitEmailFormat
-
The format of email messages for the individual commits, can be "text" or
"html". In the latter case, the emails will include diffs using colorized
HTML instead of plain text used by default. Note that this currently the
the message starting with ``+++`` or ``---`` colored in red or
green).
-multimailhook.refchangeShowGraph
+ By default, all the message is HTML-escaped. See
+ ``multimailhook.htmlInIntro`` to change this behavior.
+
+multimailhook.commitBrowseURL
+ Used to generate a link to an online repository browser in commit
+ emails. This variable must be a string. Format directives like
+ ``%(<variable>)s`` will be expanded the same way as template
+ strings. In particular, ``%(id)s`` will be replaced by the full
+ Git commit identifier (40-chars hexadecimal).
+
+ If the string does not contain any format directive, then
+ ``%(id)s`` will be automatically added to the string. If you don't
+ want ``%(id)s`` to be automatically added, use the empty format
+ directive ``%()s`` anywhere in the string.
+
+ For example, a suitable value for the git-multimail project itself
+ would be
+ ``https://github.com/git-multimail/git-multimail/commit/%(id)s``.
+
+multimailhook.htmlInIntro, multimailhook.htmlInFooter
+ When generating an HTML message, git-multimail escapes any HTML
+ sequence by default. This means that if a template contains HTML
+ like ``<a href="foo">link</a>``, the reader will see the HTML
+ source code and not a proper link.
+
+ Set ``multimailhook.htmlInIntro`` to true to allow writting HTML
+ formatting in introduction templates. Similarly, set
+ ``multimailhook.htmlInFooter`` for HTML in the footer.
+ Variables expanded in the template are still escaped. For example,
+ if a repository's path contains a ``<``, it will be rendered as
+ such in the message.
+
+ Read `<doc/customizing-emails.rst>`__ for more details and
+ examples.
+
+multimailhook.refchangeShowGraph
If this option is set to true, then summary emails about reference
changes will additionally include:
specified in graphOpts. The default is false.
multimailhook.refchangeShowLog
-
If this option is set to true, then summary emails about reference
changes will include a detailed log of the added commits in
addition to the one line summary. The log is generated by running
Default is false.
multimailhook.mailer
-
This option changes the way emails are sent. Accepted values are:
- - sendmail (the default): use the command ``/usr/sbin/sendmail`` or
+ * **sendmail (the default)**: use the command ``/usr/sbin/sendmail`` or
``/usr/lib/sendmail`` (or sendmailCommand, if configured). This
mode can be further customized via the following options:
- * multimailhook.sendmailCommand
-
- The command used by mailer ``sendmail`` to send emails. Shell
- quoting is allowed in the value of this setting, but remember that
- Git requires double-quotes to be escaped; e.g.::
+ multimailhook.sendmailCommand
+ The command used by mailer ``sendmail`` to send emails. Shell
+ quoting is allowed in the value of this setting, but remember that
+ Git requires double-quotes to be escaped; e.g.::
- git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
+ git config multimailhook.sendmailcommand '/usr/sbin/sendmail -oi -t -F \"Git Repo\"'
- Default is '/usr/sbin/sendmail -oi -t' or
- '/usr/lib/sendmail -oi -t' (depending on which file is
- present and executable).
+ Default is '/usr/sbin/sendmail -oi -t' or
+ '/usr/lib/sendmail -oi -t' (depending on which file is
+ present and executable).
- * multimailhook.envelopeSender
+ multimailhook.envelopeSender
+ If set then pass this value to sendmail via the -f option to set
+ the envelope sender address.
- If set then pass this value to sendmail via the -f option to set
- the envelope sender address.
-
- - smtp: use Python's smtplib. This is useful when the sendmail
+ * **smtp**: use Python's smtplib. This is useful when the sendmail
command is not available on the system. This mode can be
further customized via the following options:
- * multimailhook.smtpServer
-
- The name of the SMTP server to connect to. The value can
- also include a colon and a port number; e.g.,
- ``mail.example.com:25``. Default is 'localhost' using port 25.
-
- * multimailhook.smtpUser
- * multimailhook.smtpPass
-
- Server username and password. Required if smtpEncryption is 'ssl'.
- Note that the username and password currently need to be
- set cleartext in the configuration file, which is not
- recommended. If you need to use this option, be sure your
- configuration file is read-only.
+ multimailhook.smtpServer
+ The name of the SMTP server to connect to. The value can
+ also include a colon and a port number; e.g.,
+ ``mail.example.com:25``. Default is 'localhost' using port 25.
- * multimailhook.envelopeSender
+ multimailhook.smtpUser, multimailhook.smtpPass
+ Server username and password. Required if smtpEncryption is 'ssl'.
+ Note that the username and password currently need to be
+ set cleartext in the configuration file, which is not
+ recommended. If you need to use this option, be sure your
+ configuration file is read-only.
+ multimailhook.envelopeSender
The sender address to be passed to the SMTP server. If
unset, then the value of multimailhook.from is used.
- * multimailhook.smtpServerTimeout
-
+ multimailhook.smtpServerTimeout
Timeout in seconds.
- * multimailhook.smtpEncryption
-
- Set the security type. Allowed values: none, ssl, tls.
- Default=none.
-
- * multimailhook.smtpServerDebugLevel
-
+ multimailhook.smtpEncryption
+ Set the security type. Allowed values: ``none``, ``ssl``, ``tls`` (starttls).
+ Default is ``none``.
+
+ multimailhook.smtpCACerts
+ Set the path to a list of trusted CA certificate to verify the
+ server certificate, only supported when ``smtpEncryption`` is
+ ``tls``. If unset or empty, the server certificate is not
+ verified. If it targets a file containing a list of trusted CA
+ certificates (PEM format) these CAs will be used to verify the
+ server certificate. For debian, you can set
+ ``/etc/ssl/certs/ca-certificates.crt`` for using the system
+ trusted CAs. For self-signed server, you can add your server
+ certificate to the system store::
+
+ cd /usr/local/share/ca-certificates/
+ openssl s_client -starttls smtp \
+ -connect mail.example.net:587 -showcerts \
+ </dev/null 2>/dev/null \
+ | openssl x509 -outform PEM >mail.example.net.crt
+ update-ca-certificates
+
+ and used the updated ``/etc/ssl/certs/ca-certificates.crt``. Or
+ directly use your ``/path/to/mail.example.net.crt``. Default is
+ unset.
+
+ multimailhook.smtpServerDebugLevel
Integer number. Set to greater than 0 to activate debugging.
-multimailhook.from
-multimailhook.fromCommit
-multimailhook.fromRefchange
-
+multimailhook.from, multimailhook.fromCommit, multimailhook.fromRefchange
If set, use this value in the From: field of generated emails.
``fromCommit`` is used for commit emails, ``fromRefchange`` is
used for refchange emails, and ``from`` is used as fall-back in
- The value ``pusher``, in which case the pusher's address (if
available) will be used.
- - The value ``author`` (meaningful only for replyToCommit), in which
+ - The value ``author`` (meaningful only for ``fromCommit``), in which
case the commit author's address will be used.
If config values are unset, the value of the From: header is
3. Use the value of multimailhook.envelopeSender.
multimailhook.administrator
-
The name and/or email address of the administrator of the Git
repository; used in FOOTER_TEMPLATE. Default is
multimailhook.envelopesender if it is set; otherwise a generic
string is used.
multimailhook.emailPrefix
-
All emails have this string prepended to their subjects, to aid
email filtering (though filtering based on the X-Git-* email
headers is probably more robust). Default is the short name of
value to the empty string to suppress the email prefix.
multimailhook.emailMaxLines
-
The maximum number of lines that should be included in the body of
a generated email. If not specified, there is no limit. Lines
beyond the limit are suppressed and counted, and a final line is
added indicating the number of suppressed lines.
multimailhook.emailMaxLineLength
-
The maximum length of a line in the email body. Lines longer than
- this limit are truncated to this length with a trailing `` [...]``
+ this limit are truncated to this length with a trailing ``[...]``
added to indicate the missing text. The default is 500, because
(a) diffs with longer lines are probably from binary files, for
which a diff is useless, and (b) even if a text file has such long
truncation, set this option to 0.
multimailhook.maxCommitEmails
-
The maximum number of commit emails to send for a given change.
When the number of patches is larger that this value, only the
summary refchange email is sent. This can avoid accidental
emails limit, set this option to 0. The default is 500.
multimailhook.emailStrictUTF8
-
If this boolean option is set to `true`, then the main part of the
email body is forced to be valid UTF-8. Any characters that are
not valid UTF-8 are converted to the Unicode replacement
character, U+FFFD. The default is `true`.
multimailhook.diffOpts
-
Options passed to ``git diff-tree`` when generating the summary
information for ReferenceChange emails. Default is ``--stat
--summary --find-copies-harder``. Add -p to those options to
details.
multimailhook.graphOpts
-
Options passed to ``git log --graph`` when generating graphs for the
reference change summary emails (used only if refchangeShowGraph
is true). The default is '--oneline --decorate'.
Shell quoting is allowed; see logOpts for details.
multimailhook.logOpts
-
Options passed to ``git log`` to generate additional info for
reference change emails (used only if refchangeShowLog is set).
For example, adding -p will show each commit's complete diff. The
logopts = --pretty=format:\"%h %aN <%aE>%n%s%n%n%b%n\"
multimailhook.commitLogOpts
-
Options passed to ``git log`` to generate additional info for
revision change emails. For example, adding --ignore-all-spaces
will suppress whitespace changes. The default options are ``-C
multimailhook.logOpts for details.
multimailhook.dateSubstitute
-
String to use as a substitute for ``Date:`` in the output of ``git
log`` while formatting commit messages. This is usefull to avoid
emitting a line that can be interpreted by mailers as the start of
a cited message (Zimbra webmail in particular). Defaults to
- ``CommitDate: ``. Set to an empty string or ``none`` to deactivate
+ ``CommitDate:``. Set to an empty string or ``none`` to deactivate
the behavior.
multimailhook.emailDomain
-
Domain name appended to the username of the person doing the push
to convert it into an email address
(via ``"%s@%s" % (username, emaildomain)``). More complicated
schemes can be implemented by overriding Environment and
overriding its get_pusher_email() method.
-multimailhook.replyTo
-multimailhook.replyToCommit
-multimailhook.replyToRefchange
-
+multimailhook.replyTo, multimailhook.replyToCommit, multimailhook.replyToRefchange
Addresses to use in the Reply-To: field for commit emails
(replyToCommit) and refchange emails (replyToRefchange).
multimailhook.replyTo is used as default when replyToCommit or
commit emails.
multimailhook.quiet
-
Do not output the list of email recipients from the hook
multimailhook.stdout
-
For debugging, send emails to stdout rather than to the
mailer. Equivalent to the --stdout command line option
multimailhook.scanCommitForCc
-
If this option is set to true, than recipients from lines in commit body
that starts with ``CC:`` will be added to CC list.
Default: false
multimailhook.combineWhenSingleCommit
-
If this option is set to true and a single new commit is pushed to
a branch, combine the summary and commit email messages into a
single email.
Default: true
-multimailhook.refFilterInclusionRegex
-multimailhook.refFilterExclusionRegex
-multimailhook.refFilterDoSendRegex
-multimailhook.refFilterDontSendRegex
-
+multimailhook.refFilterInclusionRegex, multimailhook.refFilterExclusionRegex, multimailhook.refFilterDoSendRegex, multimailhook.refFilterDontSendRegex
**Warning:** these options are experimental. They should work, but
the user-interface is not stable yet (in particular, the option
names may change). If you want to participate in stabilizing the
the local environment in which Git is running. Two types of
environment are built in:
-* GenericEnvironment: a stand-alone Git repository.
+GenericEnvironment
+ a stand-alone Git repository.
-* GitoliteEnvironment: a Git repository that is managed by gitolite
- [3]_. For such repositories, the identity of the pusher is read from
- environment variable $GL_USER, the name of the repository is read
- from $GL_REPO (if it is not overridden by multimailhook.reponame),
- and the From: header value is optionally read from gitolite.conf
- (see multimailhook.from).
+GitoliteEnvironment
+ a Git repository that is managed by gitolite
+ [3]_. For such repositories, the identity of the pusher is read from
+ environment variable $GL_USER, the name of the repository is read
+ from $GL_REPO (if it is not overridden by multimailhook.reponame),
+ and the From: header value is optionally read from gitolite.conf
+ (see multimailhook.from).
By default, git-multimail assumes GitoliteEnvironment if $GL_USER and
$GL_REPO are set, and otherwise assumes GenericEnvironment.
https://github.com/git-multimail/git-multimail
The version in this directory was obtained from the upstream project
-on October 11 2015 and consists of the "git-multimail" subdirectory from
+on May 03 2016 and consists of the "git-multimail" subdirectory from
revision
- c0791b9ef5821a746fc3475c25765e640452eaae refs/tags/1.2.0
+ 26f3ae9f86aa7f8a054ba89235c4d3879f98b03d refs/tags/1.3.0
Please see the README file in this directory for information about how
to report bugs or contribute to git-multimail.
--- /dev/null
+Customizing the content and formatting of emails
+================================================
+
+Overloading template strings
+----------------------------
+
+The content of emails is generated based on template strings defined
+in ``git_multimail.py``. You can customize these template strings
+without changing the script itself, by defining a Python wrapper
+around it. The python wrapper should ``import git_multimail`` and then
+override the ``git_multimail.*`` strings like this::
+
+ import sys # needed for sys.argv
+
+ # Import and customize git_multimail:
+ import git_multimail
+ git_multimail.REVISION_INTRO_TEMPLATE = """..."""
+ git_multimail.COMBINED_INTRO_TEMPLATE = git_multimail.REVISION_INTRO_TEMPLATE
+
+ # start git_multimail itself:
+ git_multimail.main(sys.argv[1:])
+
+The template strings can use any value already used in the existing
+templates (read the source code).
+
+Using HTML in template strings
+------------------------------
+
+If ``multimailhook.commitEmailFormat`` is set to HTML, then
+git-multimail will generate HTML emails for commit notifications. The
+log and diff will be formatted automatically by git-multimail. By
+default, any HTML special character in the templates will be escaped.
+
+To use HTML formatting in the introduction of the email, set
+``multimailhook.htmlInIntro`` to ``true``. Then, the template can
+contain any HTML tags, that will be sent as-is in the email. For
+example, to add some formatting and a link to the online commit, use
+a format like::
+
+ git_multimail.REVISION_INTRO_TEMPLATE = """\
+ <span style="color:#808080">This is an automated email from the git hooks/post-receive script.</span><br /><br />
+
+ <strong>%(pusher)s</strong> pushed a commit to %(refname_type)s %(short_refname)s
+ in repository %(repo_shortname)s.<br />
+
+ <a href="https://github.com/git-multimail/git-multimail/commit/%(newrev)s">View on GitHub</a>.
+ """
+
+Note that the values expanded from ``%(variable)s`` in the format
+strings will still be escaped.
+
+For a less flexible but easier to set up way to add a link to commit
+emails, see ``multimailhook.commitBrowseURL``.
+
+Similarly, one can set ``multimailhook.htmlInFooter`` and override any
+of the ``*_FOOTER*`` template strings.
--- /dev/null
+Troubleshooting issues with git-multimail: a FAQ
+================================================
+
+Git is not using the right address in the From/To/Reply-To field
+----------------------------------------------------------------
+
+First, make sure that git-multimail actually uses what you think it is
+using. A lot happens to your email (especially when posting to a
+mailing-list) between the time `git_multimail.py` sends it and the
+time it reaches your inbox.
+
+A simple test (to do on a test repository, do not use in production as
+it would disable email sending): change your post-receive hook to call
+`git_multimail.py` with the `--stdout` option, and try to push to the
+repository. You should see something like::
+
+ Counting objects: 3, done.
+ Writing objects: 100% (3/3), 263 bytes | 0 bytes/s, done.
+ Total 3 (delta 0), reused 0 (delta 0)
+ remote: Sending notification emails to: foo.bar@example.com
+ remote: ===========================================================================
+ remote: Date: Mon, 25 Apr 2016 18:39:59 +0200
+ remote: To: foo.bar@example.com
+ remote: Subject: [git] branch master updated: foo
+ remote: MIME-Version: 1.0
+ remote: Content-Type: text/plain; charset=utf-8
+ remote: Content-Transfer-Encoding: 8bit
+ remote: Message-ID: <20160425163959.2311.20498@anie>
+ remote: From: Auth Or <Foo.Bar@example.com>
+ remote: Reply-To: Auth Or <Foo.Bar@example.com>
+ remote: X-Git-Host: example
+ ...
+ remote: --
+ remote: To stop receiving notification emails like this one, please contact
+ remote: the administrator of this repository.
+ remote: ===========================================================================
+ To /path/to/repo
+ 6278f04..e173f20 master -> master
+
+Note: this does not include the sender (Return-Path: header), as it is
+not part of the message content but passed to the mailer. Some mailer
+show the ``Sender:`` field instead of the ``From:`` field (for
+example, Zimbra Webmail shows ``From: <sender-field> on behalf of
+<from-field>``).
#! /usr/bin/env python
-__version__ = '1.2.0'
+__version__ = '1.3.0'
# Copyright (c) 2015 Matthieu Moy and others
# Copyright (c) 2012-2014 Michael Haggerty and others
import shlex
import optparse
import smtplib
+try:
+ import ssl
+except ImportError:
+ # Python < 2.6 do not have ssl, but that's OK if we don't use it.
+ pass
import time
import cgi
if PYTHON3:
+ def is_string(s):
+ return isinstance(s, str)
+
def str_to_bytes(s):
return s.encode(ENCODING)
except UnicodeEncodeError:
f.buffer.write(msg.encode(ENCODING))
else:
+ def is_string(s):
+ try:
+ return isinstance(s, basestring)
+ except NameError: # Silence Pyflakes warning
+ raise
+
def str_to_bytes(s):
return s
"""
+LINK_TEXT_TEMPLATE = """\
+View the commit online:
+%(browse_url)s
+
+"""
+
+LINK_HTML_TEMPLATE = """\
+<p><a href="%(browse_url)s">View the commit online</a>.</p>
+"""
+
REVISION_FOOTER_TEMPLATE = FOOTER_TEMPLATE
assert words[-1] == ''
return words[:-1]
+ @staticmethod
+ def add_config_parameters(c):
+ """Add configuration parameters to Git.
+
+ c is either an str or a list of str, each element being of the
+ form 'var=val' or 'var', with the same syntax and meaning as
+ the argument of 'git -c var=val'.
+ """
+ if isinstance(c, str):
+ c = (c,)
+ parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
+ if parameters:
+ parameters += ' '
+ # git expects GIT_CONFIG_PARAMETERS to be of the form
+ # "'name1=value1' 'name2=value2' 'name3=value3'"
+ # including everything inside the double quotes (but not the double
+ # quotes themselves). Spacing is critical. Also, if a value contains
+ # a literal single quote that quote must be represented using the
+ # four character sequence: '\''
+ parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in c)
+ os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+
def get(self, name, default=None):
try:
values = self._split(read_git_output(
values['multimail_version'] = get_version()
return values
+ # Aliases usable in template strings. Tuple of pairs (destination,
+ # source).
+ VALUES_ALIAS = (
+ ("id", "newrev"),
+ )
+
def get_values(self, **extra_values):
"""Return a dictionary {keyword: expansion} for this Change.
values = self._values.copy()
if extra_values:
values.update(extra_values)
+
+ for alias, val in self.VALUES_ALIAS:
+ values[alias] = values[val]
return values
def expand(self, template, **extra_values):
return template % self.get_values(**extra_values)
- def expand_lines(self, template, **extra_values):
+ def expand_lines(self, template, html_escape_val=False, **extra_values):
"""Break template into lines and expand each line."""
values = self.get_values(**extra_values)
+ if html_escape_val:
+ for k in values:
+ if is_string(values[k]):
+ values[k] = cgi.escape(values[k], True)
for line in template.splitlines(True):
yield line % values
values = self.get_values(**extra_values)
if self._contains_html_diff:
- values['contenttype'] = 'html'
+ self._content_type = 'html'
else:
- values['contenttype'] = 'plain'
+ self._content_type = 'plain'
+ values['contenttype'] = self._content_type
for line in template.splitlines():
(name, value) = line.split(': ', 1)
raise NotImplementedError()
- def generate_email_intro(self):
+ def generate_browse_link(self, base_url):
+ """Generate a link to an online repository browser."""
+ return iter(())
+
+ def generate_email_intro(self, html_escape_val=False):
"""Generate the email intro for this Change, a line at a time.
The output will be used as the standard boilerplate at the top
raise NotImplementedError()
- def generate_email_footer(self):
+ def generate_email_footer(self, html_escape_val):
"""Generate the footer of the email, a line at a time.
The footer is always included, irrespective of
for line in self.generate_email_header(**extra_header_values):
yield line
yield '\n'
- for line in self._wrap_for_html(self.generate_email_intro()):
+ html_escape_val = (self.environment.html_in_intro and
+ self._contains_html_diff)
+ intro = self.generate_email_intro(html_escape_val)
+ if not self.environment.html_in_intro:
+ intro = self._wrap_for_html(intro)
+ for line in intro:
yield line
+ if self.environment.commitBrowseURL:
+ for line in self.generate_browse_link(self.environment.commitBrowseURL):
+ yield line
+
body = self.generate_email_body(push)
if body_filter is not None:
body = body_filter(body)
yield line
if self._contains_html_diff:
yield '</pre>'
-
- for line in self._wrap_for_html(self.generate_email_footer()):
+ html_escape_val = (self.environment.html_in_footer and
+ self._contains_html_diff)
+ footer = self.generate_email_footer(html_escape_val)
+ if not self.environment.html_in_footer:
+ footer = self._wrap_for_html(footer)
+ for line in footer:
yield line
def get_alt_fromaddr(self):
values['rev_short'] = self.rev.short
values['change_type'] = self.change_type
values['refname'] = self.refname
+ values['newrev'] = self.rev.sha1
values['short_refname'] = self.reference_change.short_refname
values['refname_type'] = self.reference_change.refname_type
values['reply_to_msgid'] = self.reference_change.msgid
):
yield line
- def generate_email_intro(self):
- for line in self.expand_lines(REVISION_INTRO_TEMPLATE):
+ def generate_browse_link(self, base_url):
+ if '%(' not in base_url:
+ base_url += '%(id)s'
+ url = "".join(self.expand_lines(base_url))
+ if self._content_type == 'html':
+ for line in self.expand_lines(LINK_HTML_TEMPLATE,
+ html_escape_val=True,
+ browse_url=url):
+ yield line
+ elif self._content_type == 'plain':
+ for line in self.expand_lines(LINK_TEXT_TEMPLATE,
+ html_escape_val=False,
+ browse_url=url):
+ yield line
+ else:
+ raise NotImplementedError("Content-type %s unsupported. Please report it as a bug.")
+
+ def generate_email_intro(self, html_escape_val=False):
+ for line in self.expand_lines(REVISION_INTRO_TEMPLATE,
+ html_escape_val=html_escape_val):
yield line
def generate_email_body(self, push):
else:
yield line
- def generate_email_footer(self):
- return self.expand_lines(REVISION_FOOTER_TEMPLATE)
+ def generate_email_footer(self, html_escape_val):
+ return self.expand_lines(REVISION_FOOTER_TEMPLATE,
+ html_escape_val=html_escape_val)
def generate_email(self, push, body_filter=None, extra_header_values={}):
self._contains_diff()
):
yield line
- def generate_email_intro(self):
- for line in self.expand_lines(self.intro_template):
+ def generate_email_intro(self, html_escape_val=False):
+ for line in self.expand_lines(self.intro_template,
+ html_escape_val=html_escape_val):
yield line
def generate_email_body(self, push):
for line in self.generate_revision_change_summary(push):
yield line
- def generate_email_footer(self):
- return self.expand_lines(self.footer_template)
+ def generate_email_footer(self, html_escape_val):
+ return self.expand_lines(self.footer_template,
+ html_escape_val=html_escape_val)
def generate_revision_change_graph(self, push):
if self.showgraph:
smtpservertimeout=10.0, smtpserverdebuglevel=0,
smtpencryption='none',
smtpuser='', smtppass='',
+ smtpcacerts=''
):
if not envelopesender:
sys.stderr.write(
self.security = smtpencryption
self.username = smtpuser
self.password = smtppass
+ self.smtpcacerts = smtpcacerts
try:
def call(klass, server, timeout):
try:
if self.security == 'none':
self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
elif self.security == 'ssl':
+ if self.smtpcacerts:
+ raise smtplib.SMTPException(
+ "Checking certificate is not supported for ssl, prefer starttls"
+ )
self.smtp = call(smtplib.SMTP_SSL, self.smtpserver, timeout=self.smtpservertimeout)
elif self.security == 'tls':
+ if 'ssl' not in sys.modules:
+ sys.stderr.write(
+ '*** Your Python version does not have the ssl library installed\n'
+ '*** smtpEncryption=tls is not available.\n'
+ '*** Either upgrade Python to 2.6 or later\n'
+ ' or use git_multimail.py version 1.2.\n')
if ':' not in self.smtpserver:
self.smtpserver += ':587' # default port for TLS
self.smtp = call(smtplib.SMTP, self.smtpserver, timeout=self.smtpservertimeout)
+ # start: ehlo + starttls
+ # equivalent to
+ # self.smtp.ehlo()
+ # self.smtp.starttls()
+ # with acces to the ssl layer
self.smtp.ehlo()
- self.smtp.starttls()
+ if not self.smtp.has_extn("starttls"):
+ raise smtplib.SMTPException("STARTTLS extension not supported by server")
+ resp, reply = self.smtp.docmd("STARTTLS")
+ if resp != 220:
+ raise smtplib.SMTPException("Wrong answer to the STARTTLS command")
+ if self.smtpcacerts:
+ self.smtp.sock = ssl.wrap_socket(
+ self.smtp.sock,
+ ca_certs=self.smtpcacerts,
+ cert_reqs=ssl.CERT_REQUIRED
+ )
+ else:
+ self.smtp.sock = ssl.wrap_socket(
+ self.smtp.sock,
+ cert_reqs=ssl.CERT_NONE
+ )
+ sys.stderr.write(
+ '*** Warning, the server certificat is not verified (smtp) ***\n'
+ '*** set the option smtpCACerts ***\n'
+ )
+ if not hasattr(self.smtp.sock, "read"):
+ # using httplib.FakeSocket with Python 2.5.x or earlier
+ self.smtp.sock.read = self.smtp.sock.recv
+ self.smtp.file = smtplib.SSLFakeFile(self.smtp.sock)
+ self.smtp.helo_resp = None
+ self.smtp.ehlo_resp = None
+ self.smtp.esmtp_features = {}
+ self.smtp.does_esmtp = 0
+ # end: ehlo + starttls
self.smtp.ehlo()
else:
sys.stdout.write('*** Error: Control reached an invalid option. ***')
def __del__(self):
if hasattr(self, 'smtp'):
self.smtp.quit()
+ del self.smtp
def send(self, lines, to_addrs):
try:
self.smtp.login(self.username, self.password)
msg = ''.join(lines)
# turn comma-separated list into Python list if needed.
- if isinstance(to_addrs, basestring):
+ if is_string(to_addrs):
to_addrs = [email for (name, email) in getaddresses([to_addrs])]
self.smtp.sendmail(self.envelopesender, to_addrs, msg)
- except Exception:
+ except smtplib.SMTPResponseException:
sys.stderr.write('*** Error sending email ***\n')
- sys.stderr.write('*** %s\n' % sys.exc_info()[1])
- self.smtp.quit()
+ err = sys.exc_info()[1]
+ sys.stderr.write('*** Error %d: %s\n' % (err.smtp_code,
+ bytes_to_str(err.smtp_error)))
+ try:
+ smtp = self.smtp
+ # delete the field before quit() so that in case of
+ # error, self.smtp is deleted anyway.
+ del self.smtp
+ smtp.quit()
+ except:
+ sys.stderr.write('*** Error closing the SMTP connection ***\n')
+ sys.stderr.write('*** Exiting anyway ... ***\n')
+ sys.stderr.write('*** %s\n' % sys.exc_info()[1])
sys.exit(1)
If "html", generate commit emails in HTML instead of plain text
used by default.
+ html_in_intro (bool)
+ html_in_footer (bool)
+
+ When generating HTML emails, the introduction (respectively,
+ the footer) will be HTML-escaped iff html_in_intro (respectively,
+ the footer) is true. When false, only the values used to expand
+ the template are escaped.
+
refchange_showgraph (bool)
True iff refchanges emails should include a detailed graph.
self.osenv = osenv or os.environ
self.announce_show_shortlog = False
self.commit_email_format = "text"
+ self.html_in_intro = False
+ self.html_in_footer = False
+ self.commitBrowseURL = None
self.maxcommitemails = 500
self.diffopts = ['--stat', '--summary', '--find-copies-harder']
self.graphopts = ['--oneline', '--decorate']
The return value is always a new dictionary."""
if self._values is None:
- values = {}
+ values = {'': ''} # %()s expands to the empty string.
for key in self.COMPUTED_KEYS:
value = getattr(self, 'get_%s' % (key,))()
else:
self.commit_email_format = commit_email_format
+ html_in_intro = config.get_bool('htmlInIntro')
+ if html_in_intro is not None:
+ self.html_in_intro = html_in_intro
+
+ html_in_footer = config.get_bool('htmlInFooter')
+ if html_in_footer is not None:
+ self.html_in_footer = html_in_footer
+
+ self.commitBrowseURL = config.get('commitBrowseURL')
+
maxcommitemails = config.get('maxcommitemails')
if maxcommitemails is not None:
try:
['author'])
self.__reply_to_commit = config.get('replyToCommit', default=reply_to)
- from_addr = self.config.get('from')
self.from_refchange = config.get('fromRefchange')
self.forbid_field_values('fromRefchange',
self.from_refchange,
if changes:
push = Push(environment, changes)
push.send_emails(mailer, body_filter=environment.filter_body)
+ if hasattr(mailer, '__del__'):
+ mailer.__del__()
def run_as_update_hook(environment, mailer, refname, oldrev, newrev, force_send=False):
]
push = Push(environment, changes, force_send)
push.send_emails(mailer, body_filter=environment.filter_body)
+ if hasattr(mailer, '__del__'):
+ mailer.__del__()
def choose_mailer(config, environment):
smtpencryption = config.get('smtpencryption', default='none')
smtpuser = config.get('smtpuser', default='')
smtppass = config.get('smtppass', default='')
+ smtpcacerts = config.get('smtpcacerts', default='')
mailer = SMTPMailer(
envelopesender=(environment.get_sender() or environment.get_fromaddr()),
smtpserver=smtpserver, smtpservertimeout=smtpservertimeout,
smtpencryption=smtpencryption,
smtpuser=smtpuser,
smtppass=smtppass,
+ smtpcacerts=smtpcacerts
)
elif mailer == 'sendmail':
command = config.get('sendmailcommand')
return
if options.c:
- parameters = os.environ.get('GIT_CONFIG_PARAMETERS', '')
- if parameters:
- parameters += ' '
- # git expects GIT_CONFIG_PARAMETERS to be of the form
- # "'name1=value1' 'name2=value2' 'name3=value3'"
- # including everything inside the double quotes (but not the double
- # quotes themselves). Spacing is critical. Also, if a value contains
- # a literal single quote that quote must be represented using the
- # four character sequence: '\''
- parameters += ' '.join("'" + x.replace("'", "'\\''") + "'" for x in options.c)
- os.environ['GIT_CONFIG_PARAMETERS'] = parameters
+ Config.add_config_parameters(options.c)
config = Config('multimailhook')
# git-multimail:
config = git_multimail.Config('multimailhook')
+# Set some Git configuration variables. Equivalent to passing var=val
+# to "git -c var=val" each time git is called, or to adding the
+# configuration in .git/config (must come before instanciating the
+# environment) :
+#git_multimail.Config.add_config_parameters('multimailhook.commitEmailFormat=html')
+#git_multimail.Config.add_config_parameters(('user.name=foo', 'user.email=foo@example.com'))
# Select the type of environment:
try:
fprintf(out, "password=%s\n", e->item.password);
}
}
- else if (!strcmp(action.buf, "exit"))
+ else if (!strcmp(action.buf, "exit")) {
+ /*
+ * It's important that we clean up our socket first, and then
+ * signal the client only once we have finished the cleanup.
+ * Calling exit() directly does this, because we clean up in
+ * our atexit() handler, and then signal the client when our
+ * process actually ends, which closes the socket and gives
+ * them EOF.
+ */
exit(0);
+ }
else if (!strcmp(action.buf, "erase"))
remove_credential(&c);
else if (!strcmp(action.buf, "store")) {
int i, renames = 0;
struct hashmap file_table;
- /* Add all sources to the hash table */
+ /* Add all sources to the hash table in reverse order, because
+ * later on they will be retrieved in LIFO order.
+ */
hashmap_init(&file_table, NULL, rename_src_nr);
- for (i = 0; i < rename_src_nr; i++)
+ for (i = rename_src_nr-1; i >= 0; i--)
insert_file_table(&file_table, i, rename_src[i].p->one);
/* Walk the destinations and find best source match */
void parse_exclude_pattern(const char **pattern,
int *patternlen,
- int *flags,
+ unsigned *flags,
int *nowildcardlen)
{
const char *p = *pattern;
{
struct exclude *x;
int patternlen;
- int flags;
+ unsigned flags;
int nowildcardlen;
parse_exclude_pattern(&string, &patternlen, &flags, &nowildcardlen);
int match_basename(const char *basename, int basenamelen,
const char *pattern, int prefix, int patternlen,
- int flags)
+ unsigned flags)
{
if (prefix == patternlen) {
if (patternlen == basenamelen &&
int match_pathname(const char *pathname, int pathlen,
const char *base, int baselen,
const char *pattern, int prefix, int patternlen,
- int flags)
+ unsigned flags)
{
const char *name;
int namelen;
int nowildcardlen;
const char *base;
int baselen;
- int flags;
+ unsigned flags; /* EXC_FLAG_* */
/*
* Counting starts from 1 for line numbers in ignore files,
* attr.c:path_matches()
*/
extern int match_basename(const char *, int,
- const char *, int, int, int);
+ const char *, int, int, unsigned);
extern int match_pathname(const char *, int,
const char *, int,
- const char *, int, int, int);
+ const char *, int, int, unsigned);
extern struct exclude *last_exclude_matching(struct dir_struct *dir,
const char *name, int *dtype);
extern int add_excludes_from_file_to_list(const char *fname, const char *base, int baselen,
struct exclude_list *el, int check_index);
extern void add_excludes_from_file(struct dir_struct *, const char *fname);
-extern void parse_exclude_pattern(const char **string, int *patternlen, int *flags, int *nowildcardlen);
+extern void parse_exclude_pattern(const char **string, int *patternlen, unsigned *flags, int *nowildcardlen);
extern void add_exclude(const char *string, const char *base,
int baselen, struct exclude_list *el, int srcpos);
extern void clear_exclude_list(struct exclude_list *el);
int warn_ambiguous_refs = 1;
int warn_on_object_refname_ambiguity = 1;
int ref_paranoia = -1;
-int repository_format_version;
int repository_format_precious_objects;
const char *git_commit_encoding;
const char *git_log_output_encoding;
-int shared_repository = PERM_UMASK;
const char *apply_default_whitespace;
const char *apply_default_ignorewhitespace;
const char *git_attributes_file;
int core_apply_sparse_checkout;
int merge_log_config = -1;
int precomposed_unicode = -1; /* see probe_utf8_pathname_composition() */
-struct startup_info *startup_info;
unsigned long pack_size_limit_cfg;
#ifndef PROTECT_HFS_DEFAULT
{
return git_commit_encoding ? git_commit_encoding : "UTF-8";
}
+
+static int the_shared_repository = PERM_UMASK;
+static int need_shared_repository_from_config = 1;
+
+void set_shared_repository(int value)
+{
+ the_shared_repository = value;
+ need_shared_repository_from_config = 0;
+}
+
+int get_shared_repository(void)
+{
+ if (need_shared_repository_from_config) {
+ const char *var = "core.sharedrepository";
+ const char *value;
+ if (!git_config_get_value(var, &value))
+ the_shared_repository = git_config_perm(var, value);
+ need_shared_repository_from_config = 0;
+ }
+ return the_shared_repository;
+}
#endif
#include <openssl/ssl.h>
#include <openssl/err.h>
-#ifdef NO_HMAC_CTX_CLEANUP
-#define HMAC_CTX_cleanup HMAC_cleanup
-#endif
#endif
/* On most systems <netdb.h> would have given us this, but
case "$ans" in
[mMcC]*)
git add -- "$MERGED"
- cleanup_temp_files --save-backup
+ if test "$merge_keep_backup" = "true"
+ then
+ cleanup_temp_files --save-backup
+ else
+ cleanup_temp_files
+ fi
return 0
;;
[dD]*)
return 0
;;
[aA]*)
+ if test "$merge_keep_temporaries" = "false"
+ then
+ cleanup_temp_files
+ fi
return 1
;;
esac
return
fi
- mv -- "$MERGED" "$BACKUP"
- cp -- "$BACKUP" "$MERGED"
+ if test -f "$MERGED"
+ then
+ mv -- "$MERGED" "$BACKUP"
+ cp -- "$BACKUP" "$MERGED"
+ fi
+ # Create a parent directory to handle delete/delete conflicts
+ # where the base's directory no longer exists.
+ mkdir -p "$(dirname "$MERGED")"
checkout_staged_file 1 "$MERGED" "$BASE"
checkout_staged_file 2 "$MERGED" "$LOCAL"
describe_file "$local_mode" "local" "$LOCAL"
describe_file "$remote_mode" "remote" "$REMOTE"
resolve_deleted_merge
- return
+ status=$?
+ rmdir -p "$(dirname "$MERGED")" 2>/dev/null
+ return $status
fi
if is_symlink "$local_mode" || is_symlink "$remote_mode"
use 5.008;
use strict;
use warnings;
+use POSIX qw/strftime/;
use Term::ReadLine;
use Getopt::Long;
use Text::ParseWords;
-use Data::Dumper;
use Term::ANSIColor;
use File::Temp qw/ tempdir tempfile /;
use File::Spec::Functions qw(catfile);
$aliases{$alias} = \@addr
}}},
mailrc => sub { my $fh = shift; while (<$fh>) {
- if (/^alias\s+(\S+)\s+(.*)$/) {
+ if (/^alias\s+(\S+)\s+(.*?)\s*$/) {
# spaces delimit multiple addresses
$aliases{$1} = [ quotewords('\s+', 0, $2) ];
}}},
sub make_message_id {
my $uniq;
if (!defined $message_id_stamp) {
- $message_id_stamp = sprintf("%s-%s", time, $$);
+ $message_id_stamp = strftime("%Y%m%d%H%M%S.$$", gmtime(time));
$message_id_serial = 0;
}
$message_id_serial++;
require Sys::Hostname;
$du_part = 'user@' . Sys::Hostname::hostname();
}
- my $message_id_template = "<%s-git-send-email-%s>";
+ my $message_id_template = "<%s-%s>";
$message_id = sprintf($message_id_template, $uniq, $du_part);
#print "new message id = $message_id\n"; # Was useful for debugging
}
"concept guides. See 'git help <command>' or 'git help <concept>'\n"
"to read about a specific subcommand or concept.");
-static struct startup_info git_startup_info;
static int use_pager = -1;
static char *orig_cwd;
static const char *env_names[] = {
const char *cmd;
int done_help = 0;
- startup_info = &git_startup_info;
-
cmd = git_extract_argv0_path(argv[0]);
if (!cmd)
cmd = "git-help";
%files -f bin-man-doc-files
%defattr(-,root,root)
%{_datadir}/git-core/
-%doc README COPYING Documentation/*.txt
+%doc README.md COPYING Documentation/*.txt
%{!?_without_docs: %doc Documentation/*.html Documentation/howto}
%{!?_without_docs: %doc Documentation/technical}
%{_sysconfdir}/bash_completion.d
if (curl_http_proxy) {
curl_easy_setopt(result, CURLOPT_PROXY, curl_http_proxy);
#if LIBCURL_VERSION_NUM >= 0x071800
- if (starts_with(curl_http_proxy, "socks5"))
+ if (starts_with(curl_http_proxy, "socks5h"))
+ curl_easy_setopt(result,
+ CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5_HOSTNAME);
+ else if (starts_with(curl_http_proxy, "socks5"))
curl_easy_setopt(result,
CURLOPT_PROXYTYPE, CURLPROXY_SOCKS5);
else if (starts_with(curl_http_proxy, "socks4a"))
SSL_library_init();
SSL_load_error_strings();
- if (use_tls_only)
- meth = TLSv1_method();
- else
- meth = SSLv23_method();
-
+ meth = SSLv23_method();
if (!meth) {
ssl_socket_perror("SSLv23_method");
return -1;
}
ctx = SSL_CTX_new(meth);
+ if (!ctx) {
+ ssl_socket_perror("SSL_CTX_new");
+ return -1;
+ }
+
+ if (use_tls_only)
+ SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
if (verify)
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);
static char *cram(const char *challenge_64, const char *user, const char *pass)
{
int i, resp_len, encoded_len, decoded_len;
- HMAC_CTX hmac;
unsigned char hash[16];
char hex[33];
char *response, *response_64, *challenge;
(unsigned char *)challenge_64, encoded_len);
if (decoded_len < 0)
die("invalid challenge %s", challenge_64);
- HMAC_Init(&hmac, (unsigned char *)pass, strlen(pass), EVP_md5());
- HMAC_Update(&hmac, (unsigned char *)challenge, decoded_len);
- HMAC_Final(&hmac, hash, NULL);
- HMAC_CTX_cleanup(&hmac);
+ if (!HMAC(EVP_md5(), pass, strlen(pass), (unsigned char *)challenge, decoded_len, hash, NULL))
+ die("HMAC error");
hex[32] = 0;
for (i = 0; i < 16; i++) {
/* response: "<user> <digest in hex>" */
response = xstrfmt("%s %s", user, hex);
- resp_len = strlen(response) + 1;
+ resp_len = strlen(response);
response_64 = xmallocz(ENCODED_SIZE(resp_len));
encoded_len = EVP_EncodeBlock((unsigned char *)response_64,
srvc->pass = xstrdup(cred.password);
}
- if (CAP(NOLOGIN)) {
- fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host);
- goto bail;
- }
-
if (srvc->auth_method) {
struct imap_cmd_cb cb;
goto bail;
}
} else {
+ if (CAP(NOLOGIN)) {
+ fprintf(stderr, "Skipping account %s@%s, server forbids LOGIN\n",
+ srvc->user, srvc->host);
+ goto bail;
+ }
if (!imap->buf.sock.ssl)
imap_warn("*** IMAP Warning *** Password is being "
"sent in the clear\n");
void unable_to_lock_message(const char *path, int err, struct strbuf *buf)
{
if (err == EEXIST) {
- strbuf_addf(buf, "Unable to create '%s.lock': %s.\n\n"
- "If no other git process is currently running, this probably means a\n"
- "git process crashed in this repository earlier. Make sure no other git\n"
- "process is running and remove the file manually to continue.",
+ strbuf_addf(buf, _("Unable to create '%s.lock': %s.\n\n"
+ "Another git process seems to be running in this repository, e.g.\n"
+ "an editor opened by 'git commit'. Please make sure all processes\n"
+ "are terminated then try again. If it still fails, a git process\n"
+ "may have crashed in this repository earlier:\n"
+ "remove the file manually to continue."),
absolute_path(path), strerror(err));
} else
- strbuf_addf(buf, "Unable to create '%s.lock': %s",
+ strbuf_addf(buf, _("Unable to create '%s.lock': %s"),
absolute_path(path), strerror(err));
}
git_mailmap_blob = "HEAD:.mailmap";
err |= read_mailmap_file(map, ".mailmap", repo_abbrev);
- err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev);
+ if (startup_info->have_repository)
+ err |= read_mailmap_blob(map, git_mailmap_blob, repo_abbrev);
err |= read_mailmap_file(map, git_mailmap_file, repo_abbrev);
return err;
}
{
int tweak;
- if (shared_repository < 0)
- tweak = -shared_repository;
+ if (get_shared_repository() < 0)
+ tweak = -get_shared_repository();
else
- tweak = shared_repository;
+ tweak = get_shared_repository();
if (!(mode & S_IWUSR))
tweak &= ~0222;
if (mode & S_IXUSR)
/* Copy read bits to execute bits */
tweak |= (tweak & 0444) >> 2;
- if (shared_repository < 0)
+ if (get_shared_repository() < 0)
mode = (mode & ~0777) | tweak;
else
mode |= tweak;
{
int old_mode, new_mode;
- if (!shared_repository)
+ if (!get_shared_repository())
return 0;
if (get_st_mode_bits(path, &old_mode) < 0)
return -1;
#: builtin/am.c:2321 builtin/commit.c:1593 builtin/merge.c:225
#: builtin/pull.c:159 builtin/revert.c:92 builtin/tag.c:355
msgid "key-id"
-msgstr "id de clé"
+msgstr "id-clé"
#: builtin/am.c:2322
msgid "GPG-sign commits"
#: builtin/checkout.c:1154
msgid "conflict style (merge or diff3)"
-msgstr "style de conflit (fusion ou diff3)"
+msgstr "style de conflit (merge (fusion) ou diff3)"
#: builtin/checkout.c:1157
msgid "do not limit pathspecs to sparse entries only"
#: builtin/fetch.c:122 builtin/log.c:1236
msgid "dir"
-msgstr "dir"
+msgstr "répertoire"
#: builtin/fetch.c:123
msgid "prepend this to submodule path output"
#: builtin/show-ref.c:165
msgid "only show tags (can be combined with heads)"
-msgstr "afficher seulement les étiquettes (peut être combiné avec des têtes)"
+msgstr "afficher seulement les étiquettes (peut être combiné avec heads)"
#: builtin/show-ref.c:166
msgid "only show heads (can be combined with tags)"
-msgstr "afficher seulement les têtes (peut être combiné avec des étiquettes)"
+msgstr "afficher seulement les têtes (peut être combiné avec tags)"
#: builtin/show-ref.c:167
msgid "stricter reference checking, requires exact ref path"
extern int create_symref(const char *refname, const char *target, const char *logmsg);
+/*
+ * Update HEAD of the specified gitdir.
+ * Similar to create_symref("relative-git-dir/HEAD", target, NULL), but
+ * this can update the main working tree's HEAD regardless of where
+ * $GIT_DIR points to.
+ * Return 0 if successful, non-zero otherwise.
+ * */
+extern int set_worktree_head_symref(const char *gitdir, const char *target);
+
enum action_on_err {
UPDATE_REFS_MSG_ON_ERR,
UPDATE_REFS_DIE_ON_ERR,
return ret;
}
+int set_worktree_head_symref(const char *gitdir, const char *target)
+{
+ static struct lock_file head_lock;
+ struct ref_lock *lock;
+ struct strbuf head_path = STRBUF_INIT;
+ const char *head_rel;
+ int ret;
+
+ strbuf_addf(&head_path, "%s/HEAD", absolute_path(gitdir));
+ if (hold_lock_file_for_update(&head_lock, head_path.buf,
+ LOCK_NO_DEREF) < 0) {
+ struct strbuf err = STRBUF_INIT;
+ unable_to_lock_message(head_path.buf, errno, &err);
+ error("%s", err.buf);
+ strbuf_release(&err);
+ strbuf_release(&head_path);
+ return -1;
+ }
+
+ /* head_rel will be "HEAD" for the main tree, "worktrees/wt/HEAD" for
+ linked trees */
+ head_rel = remove_leading_path(head_path.buf,
+ absolute_path(get_git_common_dir()));
+ /* to make use of create_symref_locked(), initialize ref_lock */
+ lock = xcalloc(1, sizeof(struct ref_lock));
+ lock->lk = &head_lock;
+ lock->ref_name = xstrdup(head_rel);
+ lock->orig_ref_name = xstrdup(head_rel);
+
+ ret = create_symref_locked(lock, head_rel, target, NULL);
+
+ unlock_ref(lock); /* will free lock */
+ strbuf_release(&head_path);
+ return ret;
+}
+
int reflog_exists(const char *refname)
{
struct stat st;
{
static int loaded;
struct object_id oid;
- const char *head_ref;
int flag;
if (loaded)
loaded = 1;
current_branch = NULL;
- head_ref = resolve_ref_unsafe("HEAD", 0, oid.hash, &flag);
- if (head_ref && (flag & REF_ISSYMREF) &&
- skip_prefix(head_ref, "refs/heads/", &head_ref)) {
- current_branch = make_branch(head_ref, 0);
+ if (startup_info->have_repository) {
+ const char *head_ref = resolve_ref_unsafe("HEAD", 0, oid.hash, &flag);
+ if (head_ref && (flag & REF_ISSYMREF) &&
+ skip_prefix(head_ref, "refs/heads/", &head_ref)) {
+ current_branch = make_branch(head_ref, 0);
+ }
}
git_config(handle_config, NULL);
alias_all_urls();
static int inside_git_dir = -1;
static int inside_work_tree = -1;
static int work_tree_config_is_bogus;
-static struct string_list unknown_extensions = STRING_LIST_INIT_DUP;
+
+static struct startup_info the_startup_info;
+struct startup_info *startup_info = &the_startup_info;
/*
* The input parameter must contain an absolute path, and it must already be
return NULL;
}
} else {
- sanitized = xstrfmt("%.*s%s", len, prefix, path);
+ sanitized = xstrfmt("%.*s%s", len, len ? prefix : "", path);
if (remaining_prefix)
*remaining_prefix = len;
if (normalize_path_copy_len(sanitized, sanitized, remaining_prefix)) {
initialized = 1;
}
-static int check_repo_format(const char *var, const char *value, void *cb)
+static int check_repo_format(const char *var, const char *value, void *vdata)
{
+ struct repository_format *data = vdata;
const char *ext;
if (strcmp(var, "core.repositoryformatversion") == 0)
- repository_format_version = git_config_int(var, value);
- else if (strcmp(var, "core.sharedrepository") == 0)
- shared_repository = git_config_perm(var, value);
+ data->version = git_config_int(var, value);
else if (skip_prefix(var, "extensions.", &ext)) {
/*
* record any known extensions here; otherwise,
if (!strcmp(ext, "noop"))
;
else if (!strcmp(ext, "preciousobjects"))
- repository_format_precious_objects = git_config_bool(var, value);
+ data->precious_objects = git_config_bool(var, value);
else
- string_list_append(&unknown_extensions, ext);
+ string_list_append(&data->unknown_extensions, ext);
+ } else if (strcmp(var, "core.bare") == 0) {
+ data->is_bare = git_config_bool(var, value);
+ } else if (strcmp(var, "core.worktree") == 0) {
+ if (!value)
+ return config_error_nonbool(var);
+ data->work_tree = xstrdup(value);
}
return 0;
}
static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
{
struct strbuf sb = STRBUF_INIT;
- const char *repo_config;
- config_fn_t fn;
- int ret = 0;
-
- string_list_clear(&unknown_extensions, 0);
+ struct strbuf err = STRBUF_INIT;
+ struct repository_format candidate;
+ int has_common;
- if (get_common_dir(&sb, gitdir))
- fn = check_repo_format;
- else
- fn = check_repository_format_version;
+ has_common = get_common_dir(&sb, gitdir);
strbuf_addstr(&sb, "/config");
- repo_config = sb.buf;
+ read_repository_format(&candidate, sb.buf);
+ strbuf_release(&sb);
/*
- * git_config() can't be used here because it calls git_pathdup()
- * to get $GIT_CONFIG/config. That call will make setup_git_env()
- * set git_dir to ".git".
- *
- * We are in gitdir setup, no git dir has been found useable yet.
- * Use a gentler version of git_config() to check if this repo
- * is a good one.
+ * For historical use of check_repository_format() in git-init,
+ * we treat a missing config as a silent "ok", even when nongit_ok
+ * is unset.
*/
- git_config_early(fn, NULL, repo_config);
- if (GIT_REPO_VERSION_READ < repository_format_version) {
- if (!nongit_ok)
- die ("Expected git repo version <= %d, found %d",
- GIT_REPO_VERSION_READ, repository_format_version);
- warning("Expected git repo version <= %d, found %d",
- GIT_REPO_VERSION_READ, repository_format_version);
- warning("Please upgrade Git");
- *nongit_ok = -1;
- ret = -1;
+ if (candidate.version < 0)
+ return 0;
+
+ if (verify_repository_format(&candidate, &err) < 0) {
+ if (nongit_ok) {
+ warning("%s", err.buf);
+ strbuf_release(&err);
+ *nongit_ok = -1;
+ return -1;
+ }
+ die("%s", err.buf);
}
- if (repository_format_version >= 1 && unknown_extensions.nr) {
+ repository_format_precious_objects = candidate.precious_objects;
+ string_list_clear(&candidate.unknown_extensions, 0);
+ if (!has_common) {
+ if (candidate.is_bare != -1) {
+ is_bare_repository_cfg = candidate.is_bare;
+ if (is_bare_repository_cfg == 1)
+ inside_work_tree = -1;
+ }
+ if (candidate.work_tree) {
+ free(git_work_tree_cfg);
+ git_work_tree_cfg = candidate.work_tree;
+ inside_work_tree = -1;
+ }
+ } else {
+ free(candidate.work_tree);
+ }
+
+ return 0;
+}
+
+int read_repository_format(struct repository_format *format, const char *path)
+{
+ memset(format, 0, sizeof(*format));
+ format->version = -1;
+ format->is_bare = -1;
+ string_list_init(&format->unknown_extensions, 1);
+ git_config_from_file(check_repo_format, path, format);
+ return format->version;
+}
+
+int verify_repository_format(const struct repository_format *format,
+ struct strbuf *err)
+{
+ if (GIT_REPO_VERSION_READ < format->version) {
+ strbuf_addf(err, _("Expected git repo version <= %d, found %d"),
+ GIT_REPO_VERSION_READ, format->version);
+ return -1;
+ }
+
+ if (format->version >= 1 && format->unknown_extensions.nr) {
int i;
- if (!nongit_ok)
- die("unknown repository extension: %s",
- unknown_extensions.items[0].string);
+ strbuf_addstr(err, _("unknown repository extensions found:"));
- for (i = 0; i < unknown_extensions.nr; i++)
- warning("unknown repository extension: %s",
- unknown_extensions.items[i].string);
- *nongit_ok = -1;
- ret = -1;
+ for (i = 0; i < format->unknown_extensions.nr; i++)
+ strbuf_addf(err, "\n\t%s",
+ format->unknown_extensions.items[i].string);
+ return -1;
}
- strbuf_release(&sb);
- return ret;
+ return 0;
}
/*
else
setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
- if (startup_info) {
- startup_info->have_repository = !nongit_ok || !*nongit_ok;
- startup_info->prefix = prefix;
- }
+ startup_info->have_repository = !nongit_ok || !*nongit_ok;
+ startup_info->prefix = prefix;
+
return prefix;
}
return -(i & 0666);
}
-int check_repository_format_version(const char *var, const char *value, void *cb)
-{
- int ret = check_repo_format(var, value, cb);
- if (ret)
- return ret;
- if (strcmp(var, "core.bare") == 0) {
- is_bare_repository_cfg = git_config_bool(var, value);
- if (is_bare_repository_cfg == 1)
- inside_work_tree = -1;
- } else if (strcmp(var, "core.worktree") == 0) {
- if (!value)
- return config_error_nonbool(var);
- free(git_work_tree_cfg);
- git_work_tree_cfg = xstrdup(value);
- inside_work_tree = -1;
- }
- return 0;
-}
-
-int check_repository_format(void)
+void check_repository_format(void)
{
- return check_repository_format_gently(get_git_dir(), NULL);
+ check_repository_format_gently(get_git_dir(), NULL);
+ startup_info->have_repository = 1;
}
/*
if (!starts_with(rel, "./") && !starts_with(rel, "../"))
return NULL;
- if (!startup_info)
- die("BUG: startup_info struct is not initialized.");
-
if (!is_inside_work_tree())
die("relative path syntax can't be used outside working tree.");
if (errno == ENOMEM)
die("Out of memory, getdelim failed");
- /* Restore slopbuf that we moved out of the way before */
+ /*
+ * Restore strbuf invariants; if getdelim left us with a NULL pointer,
+ * we can just re-init, but otherwise we should make sure that our
+ * length is empty, and that the result is NUL-terminated.
+ */
if (!sb->buf)
strbuf_init(sb, 0);
+ else
+ strbuf_reset(sb);
return EOF;
}
#else
struct hashmap_iter iter;
struct submodule_entry *entry;
- hashmap_iter_init(&cache->for_name, &iter);
- entry = hashmap_iter_next(&iter);
+ entry = hashmap_iter_first(&cache->for_name, &iter);
if (!entry)
return NULL;
return entry->config;
PassEnv GNUPGHOME
PassEnv ASAN_OPTIONS
PassEnv GIT_TRACE
+PassEnv GIT_CONFIG_NOSYSTEM
Alias /dumb/ www/
Alias /auth/dumb/ www/auth/dumb/
test_expect_success 'GIT_PREFIX for built-ins' '
# Use GIT_EXTERNAL_DIFF to test that the "diff" built-in
# receives the GIT_PREFIX variable.
- printf "dir/" >expect &&
- printf "#!/bin/sh\n" >diff &&
- printf "printf \"\$GIT_PREFIX\"" >>diff &&
- chmod +x diff &&
+ echo "dir/" >expect &&
+ write_script diff <<-\EOF &&
+ printf "%s\n" "$GIT_PREFIX"
+ EOF
(
cd dir &&
- printf "change" >two &&
+ echo "change" >two &&
GIT_EXTERNAL_DIFF=./diff git diff >../actual
git checkout -- two
) &&
cookieFile = /tmp/cookie.txt
EOF
+ test_expect_code 1 git config --bool --get-urlmatch doesnt.exist https://good.example.com >actual &&
+ test_must_be_empty actual &&
+
echo true >expect &&
git config --bool --get-urlmatch http.SSLverify https://good.example.com >actual &&
test_cmp expect actual &&
grep "relative path syntax can.t be used outside working tree." error
'
-test_expect_success 'relative path when startup_info is NULL' '
- test_must_fail test-match-trees HEAD:./file.txt HEAD:./file.txt 2>error &&
- grep "BUG: startup_info struct is not initialized." error
-'
-
test_expect_success '<commit>:file correctly diagnosed after a pathname' '
test_must_fail git rev-parse file.txt HEAD:file.txt 1>actual 2>error &&
test_i18ngrep ! "exists on disk" error &&
--- /dev/null
+#!/bin/sh
+
+test_description='check that certain rev-parse options work outside repo'
+. ./test-lib.sh
+
+test_expect_success 'set up non-repo directory' '
+ GIT_CEILING_DIRECTORIES=$(pwd) &&
+ export GIT_CEILING_DIRECTORIES &&
+ mkdir non-repo &&
+ cd non-repo &&
+ # confirm that git does not find a repo
+ test_must_fail git rev-parse --git-dir
+'
+
+# Rather than directly test the output of sq-quote directly,
+# make sure the shell can read back a tricky case, since
+# that's what we really care about anyway.
+tricky="really tricky with \\ and \" and '"
+dump_args () {
+ for i in "$@"; do
+ echo "arg: $i"
+ done
+}
+test_expect_success 'rev-parse --sq-quote' '
+ dump_args "$tricky" easy >expect &&
+ eval "dump_args $(git rev-parse --sq-quote "$tricky" easy)" >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'rev-parse --local-env-vars' '
+ git rev-parse --local-env-vars >actual &&
+ # we do not want to depend on the complete list here,
+ # so just look for something plausible
+ grep ^GIT_DIR actual
+'
+
+test_expect_success 'rev-parse --resolve-git-dir' '
+ git init --separate-git-dir repo dir &&
+ test_must_fail git rev-parse --resolve-git-dir . &&
+ echo "$(pwd)/repo" >expect &&
+ git rev-parse --resolve-git-dir dir/.git >actual &&
+ test_cmp expect actual
+'
+
+test_done
test_expect_success 'git branch -M baz bam should succeed when baz is checked out' '
git checkout -b baz &&
git branch bam &&
- git branch -M baz bam
+ git branch -M baz bam &&
+ test $(git rev-parse --abbrev-ref HEAD) = bam
+'
+
+test_expect_success 'git branch -M baz bam should succeed when baz is checked out as linked working tree' '
+ git checkout master &&
+ git worktree add -b baz bazdir &&
+ git worktree add -f bazdir2 baz &&
+ git branch -M baz bam &&
+ test $(git -C bazdir rev-parse --abbrev-ref HEAD) = bam &&
+ test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam
+'
+
+test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' '
+ git checkout -b baz &&
+ git worktree add -f bazdir3 baz &&
+ (
+ cd bazdir3 &&
+ git branch -M baz bam &&
+ test $(git rev-parse --abbrev-ref HEAD) = bam
+ ) &&
+ test $(git rev-parse --abbrev-ref HEAD) = bam
'
test_expect_success 'git branch -M master should work when master is checked out' '
test_i18ncmp expect actual
'
+test_expect_success 'deleting currently checked out branch fails' '
+ git worktree add -b my7 my7 &&
+ test_must_fail git -C my7 branch -d my7 &&
+ test_must_fail git branch -d my7
+'
+
test_expect_success 'test --track without .fetch entries' '
git branch --track my8 &&
test "$(git config branch.my8.remote)" &&
test_cmp expect actual
'
+test_expect_success 'local-branch symrefs shortened properly' '
+ git symbolic-ref refs/heads/ref-to-branch refs/heads/branch-one &&
+ git symbolic-ref refs/heads/ref-to-remote refs/remotes/origin/branch-one &&
+ cat >expect <<-\EOF &&
+ ref-to-branch -> branch-one
+ ref-to-remote -> refs/remotes/origin/branch-one
+ EOF
+ git branch >actual.raw &&
+ grep ref-to <actual.raw >actual &&
+ test_cmp expect actual
+'
+
test_done
test_expect_success 'rebase a commit violating pre-commit' '
mkdir -p .git/hooks &&
- PRE_COMMIT=.git/hooks/pre-commit &&
- echo "#!/bin/sh" > $PRE_COMMIT &&
- echo "test -z \"\$(git diff --cached --check)\"" >> $PRE_COMMIT &&
- chmod a+x $PRE_COMMIT &&
+ write_script .git/hooks/pre-commit <<-\EOF &&
+ test -z "$(git diff --cached --check)"
+ EOF
echo "monde! " >> file1 &&
test_tick &&
test_must_fail git commit -m doesnt-verify file1 &&
git show HEAD:path1 | sed "s/15/16/" > subdir/path1 &&
git status | test_i18ngrep "renamed: .*path1 -> subdir/path1"'
+test_expect_success 'two files with same basename and same content' '
+ git reset --hard &&
+ mkdir -p dir/A dir/B &&
+ cp path1 dir/A/file &&
+ cp path1 dir/B/file &&
+ git add dir &&
+ git commit -m 2 &&
+ git mv dir other-dir &&
+ git status | test_i18ngrep "renamed: .*dir/A/file -> other-dir/A/file"
+'
+
test_expect_success 'setup for many rename source candidates' '
git reset --hard &&
for i in 0 1 2 3 4 5 6 7 8 9;
git index-pack test-3.pack &&
cmp test-3.idx test-3-${packname_3}.idx &&
+ cat test-1-${packname_1}.pack >test-4.pack &&
+ rm -f test-4.keep &&
+ git index-pack --keep=why test-4.pack &&
+ cmp test-1-${packname_1}.idx test-4.idx &&
+ test -f test-4.keep &&
+
:'
test_expect_success 'unpacking with --strict' '
test "$victim_orig" = "$victim_head"
'
+test_expect_success 'send-pack --all sends all branches' '
+ # make sure we have at least 2 branches with different
+ # values, just to be thorough
+ git branch other-branch HEAD^ &&
+
+ git init --bare all.git &&
+ git send-pack --all all.git &&
+ git for-each-ref refs/heads >expect &&
+ git -C all.git for-each-ref refs/heads >actual &&
+ test_cmp expect actual
+'
+
test_expect_success 'push --all excludes remote-tracking hierarchy' '
mkdir parent &&
(
git fsck
)
'
+
+test_expect_success 'fetch-pack can fetch a raw sha1' '
+ git init hidden &&
+ (
+ cd hidden &&
+ test_commit 1 &&
+ test_commit 2 &&
+ git update-ref refs/hidden/one HEAD^ &&
+ git config transfer.hiderefs refs/hidden &&
+ git config uploadpack.allowtipsha1inwant true
+ ) &&
+ git fetch-pack hidden $(git -C hidden rev-parse refs/hidden/one)
+'
+
check_prot_path () {
cat >expected <<-EOF &&
Diag: url=$1
)
'
-cat >proxy <<'EOF'
-#!/bin/sh
-echo >&2 "proxying for $*"
-cmd=$("$PERL_PATH" -e '
+test_expect_success 'setup proxy script' '
+ write_script proxy-get-cmd "$PERL_PATH" <<-\EOF &&
read(STDIN, $buf, 4);
my $n = hex($buf) - 4;
read(STDIN, $buf, $n);
# drop absolute-path on repo name
$cmd =~ s{ /}{ };
print $cmd;
-')
-echo >&2 "Running '$cmd'"
-exec $cmd
-EOF
-chmod +x proxy
+ EOF
+
+ write_script proxy <<-\EOF
+ echo >&2 "proxying for $*"
+ cmd=$(./proxy-get-cmd)
+ echo >&2 "Running $cmd"
+ exec $cmd
+ EOF
+'
+
test_expect_success 'setup local repo' '
git remote add fake git://example.com/remote &&
git config core.gitproxy ./proxy
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test clone --reference'
+. ./test-lib.sh
+
+base_dir=$(pwd)
+
+U=$base_dir/UPLOAD_LOG
+
+# create a commit in repo $1 with name $2
+commit_in () {
+ (
+ cd "$1" &&
+ echo "$2" >"$2" &&
+ git add "$2" &&
+ git commit -m "$2"
+ )
+}
+
+# check that there are $2 loose objects in repo $1
+test_objcount () {
+ echo "$2" >expect &&
+ git -C "$1" count-objects >actual.raw &&
+ cut -d' ' -f1 <actual.raw >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'preparing first repository' '
+ test_create_repo A &&
+ commit_in A file1
+'
+
+test_expect_success 'preparing second repository' '
+ git clone A B &&
+ commit_in B file2 &&
+ git -C B repack -ad &&
+ git -C B prune
+'
+
+test_expect_success 'cloning with reference (-l -s)' '
+ git clone -l -s --reference B A C
+'
+
+test_expect_success 'existence of info/alternates' '
+ test_line_count = 2 C/.git/objects/info/alternates
+'
+
+test_expect_success 'pulling from reference' '
+ git -C C pull ../B master
+'
+
+test_expect_success 'that reference gets used' '
+ test_objcount C 0
+'
+
+test_expect_success 'cloning with reference (no -l -s)' '
+ GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
+'
+
+test_expect_success 'fetched no objects' '
+ test -s "$U.D" &&
+ ! grep " want" "$U.D"
+'
+
+test_expect_success 'existence of info/alternates' '
+ test_line_count = 1 D/.git/objects/info/alternates
+'
+
+test_expect_success 'pulling from reference' '
+ git -C D pull ../B master
+'
+
+test_expect_success 'that reference gets used' '
+ test_objcount D 0
+'
+
+test_expect_success 'updating origin' '
+ commit_in A file3 &&
+ git -C A repack -ad &&
+ git -C A prune
+'
+
+test_expect_success 'pulling changes from origin' '
+ git -C C pull origin
+'
+
+# the 2 local objects are commit and tree from the merge
+test_expect_success 'that alternate to origin gets used' '
+ test_objcount C 2
+'
+
+test_expect_success 'pulling changes from origin' '
+ git -C D pull origin
+'
+
+# the 5 local objects are expected; file3 blob, commit in A to add it
+# and its tree, and 2 are our tree and the merge commit.
+test_expect_success 'check objects expected to exist locally' '
+ test_objcount D 5
+'
+
+test_expect_success 'preparing alternate repository #1' '
+ test_create_repo F &&
+ commit_in F file1
+'
+
+test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' '
+ git clone F G &&
+ commit_in F file2
+'
+
+test_expect_success 'cloning alternate repo #1, using #2 as reference' '
+ git clone --reference G F H
+'
+
+test_expect_success 'cloning with reference being subset of source (-l -s)' '
+ git clone -l -s --reference A B E
+'
+
+test_expect_success 'cloning with multiple references drops duplicates' '
+ git clone -s --reference B --reference A --reference B A dups &&
+ test_line_count = 2 dups/.git/objects/info/alternates
+'
+
+test_expect_success 'clone with reference from a tagged repository' '
+ (
+ cd A && git tag -a -m tagged HEAD
+ ) &&
+ git clone --reference=A A I
+'
+
+test_expect_success 'prepare branched repository' '
+ git clone A J &&
+ (
+ cd J &&
+ git checkout -b other master^ &&
+ echo other >otherfile &&
+ git add otherfile &&
+ git commit -m other &&
+ git checkout master
+ )
+'
+
+test_expect_success 'fetch with incomplete alternates' '
+ git init K &&
+ echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
+ (
+ cd K &&
+ git remote add J "file://$base_dir/J" &&
+ GIT_TRACE_PACKET=$U.K git fetch J
+ ) &&
+ master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
+ test -s "$U.K" &&
+ ! grep " want $master_object" "$U.K" &&
+ tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
+ ! grep " want $tag_object" "$U.K"
+'
+
+test_expect_success 'clone using repo with gitfile as a reference' '
+ git clone --separate-git-dir=L A M &&
+ git clone --reference=M A N &&
+ echo "$base_dir/L/objects" >expected &&
+ test_cmp expected "$base_dir/N/.git/objects/info/alternates"
+'
+
+test_expect_success 'clone using repo pointed at by gitfile as reference' '
+ git clone --reference=M/.git A O &&
+ echo "$base_dir/L/objects" >expected &&
+ test_cmp expected "$base_dir/O/.git/objects/info/alternates"
+'
+
+test_expect_success 'clone and dissociate from reference' '
+ git init P &&
+ (
+ cd P && test_commit one
+ ) &&
+ git clone P Q &&
+ (
+ cd Q && test_commit two
+ ) &&
+ git clone --no-local --reference=P Q R &&
+ git clone --no-local --reference=P --dissociate Q S &&
+ # removing the reference P would corrupt R but not S
+ rm -fr P &&
+ test_must_fail git -C R fsck &&
+ git -C S fsck
+'
+test_expect_success 'clone, dissociate from partial reference and repack' '
+ rm -fr P Q R &&
+ git init P &&
+ (
+ cd P &&
+ test_commit one &&
+ git repack &&
+ test_commit two &&
+ git repack
+ ) &&
+ git clone --bare P Q &&
+ (
+ cd P &&
+ git checkout -b second &&
+ test_commit three &&
+ git repack
+ ) &&
+ git clone --bare --dissociate --reference=P Q R &&
+ ls R/objects/pack/*.pack >packs.txt &&
+ test_line_count = 1 packs.txt
+'
+
+test_expect_success 'clone, dissociate from alternates' '
+ rm -fr A B C &&
+ test_create_repo A &&
+ commit_in A file1 &&
+ git clone --reference=A A B &&
+ test_line_count = 1 B/.git/objects/info/alternates &&
+ git clone --local --dissociate B C &&
+ ! test -f C/.git/objects/info/alternates &&
+ ( cd C && git fsck )
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test local clone'
+. ./test-lib.sh
+
+repo_is_hardlinked() {
+ find "$1/objects" -type f -links 1 >output &&
+ test_line_count = 0 output
+}
+
+test_expect_success 'preparing origin repository' '
+ : >file && git add . && git commit -m1 &&
+ git clone --bare . a.git &&
+ git clone --bare . x &&
+ test "$(cd a.git && git config --bool core.bare)" = true &&
+ test "$(cd x && git config --bool core.bare)" = true &&
+ git bundle create b1.bundle --all &&
+ git bundle create b2.bundle master &&
+ mkdir dir &&
+ cp b1.bundle dir/b3 &&
+ cp b1.bundle b4
+'
+
+test_expect_success 'local clone without .git suffix' '
+ git clone -l -s a b &&
+ (cd b &&
+ test "$(git config --bool core.bare)" = false &&
+ git fetch)
+'
+
+test_expect_success 'local clone with .git suffix' '
+ git clone -l -s a.git c &&
+ (cd c && git fetch)
+'
+
+test_expect_success 'local clone from x' '
+ git clone -l -s x y &&
+ (cd y && git fetch)
+'
+
+test_expect_success 'local clone from x.git that does not exist' '
+ test_must_fail git clone -l -s x.git z
+'
+
+test_expect_success 'With -no-hardlinks, local will make a copy' '
+ git clone --bare --no-hardlinks x w &&
+ ! repo_is_hardlinked w
+'
+
+test_expect_success 'Even without -l, local will make a hardlink' '
+ rm -fr w &&
+ git clone -l --bare x w &&
+ repo_is_hardlinked w
+'
+
+test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
+ echo "ref: refs/heads/nonexistent" > a.git/HEAD &&
+ git clone a d &&
+ (cd d &&
+ git fetch &&
+ test ! -e .git/refs/remotes/origin/HEAD)
+'
+
+test_expect_success 'bundle clone without .bundle suffix' '
+ git clone dir/b3 &&
+ (cd b3 && git fetch)
+'
+
+test_expect_success 'bundle clone with .bundle suffix' '
+ git clone b1.bundle &&
+ (cd b1 && git fetch)
+'
+
+test_expect_success 'bundle clone from b4' '
+ git clone b4 bdl &&
+ (cd bdl && git fetch)
+'
+
+test_expect_success 'bundle clone from b4.bundle that does not exist' '
+ test_must_fail git clone b4.bundle bb
+'
+
+test_expect_success 'bundle clone with nonexistent HEAD' '
+ git clone b2.bundle b2 &&
+ (cd b2 &&
+ git fetch &&
+ test_must_fail git rev-parse --verify refs/heads/master)
+'
+
+test_expect_success 'clone empty repository' '
+ mkdir empty &&
+ (cd empty &&
+ git init &&
+ git config receive.denyCurrentBranch warn) &&
+ git clone empty empty-clone &&
+ test_tick &&
+ (cd empty-clone
+ echo "content" >> foo &&
+ git add foo &&
+ git commit -m "Initial commit" &&
+ git push origin master &&
+ expected=$(git rev-parse master) &&
+ actual=$(git --git-dir=../empty/.git rev-parse master) &&
+ test $actual = $expected)
+'
+
+test_expect_success 'clone empty repository, and then push should not segfault.' '
+ rm -fr empty/ empty-clone/ &&
+ mkdir empty &&
+ (cd empty && git init) &&
+ git clone empty empty-clone &&
+ (cd empty-clone &&
+ test_must_fail git push)
+'
+
+test_expect_success 'cloning non-existent directory fails' '
+ rm -rf does-not-exist &&
+ test_must_fail git clone does-not-exist
+'
+
+test_expect_success 'cloning non-git directory fails' '
+ rm -rf not-a-git-repo not-a-git-repo-clone &&
+ mkdir not-a-git-repo &&
+ test_must_fail git clone not-a-git-repo not-a-git-repo-clone
+'
+
+test_expect_success 'cloning file:// does not hardlink' '
+ git clone --bare file://"$(pwd)"/a non-local &&
+ ! repo_is_hardlinked non-local
+'
+
+test_expect_success 'cloning a local path with --no-local does not hardlink' '
+ git clone --bare --no-local a force-nonlocal &&
+ ! repo_is_hardlinked force-nonlocal
+'
+
+test_expect_success 'cloning locally respects "-u" for fetching refs' '
+ test_must_fail git clone --bare -u false a should_not_work.git
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='basic clone options'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >file && git add file &&
+ git commit -m one)
+
+'
+
+test_expect_success 'clone -o' '
+
+ git clone -o foo parent clone-o &&
+ (cd clone-o && git rev-parse --verify refs/remotes/foo/master)
+
+'
+
+test_expect_success 'redirected clone does not show progress' '
+
+ git clone "file://$(pwd)/parent" clone-redirected >out 2>err &&
+ ! grep % err &&
+ test_i18ngrep ! "Checking connectivity" err
+
+'
+
+test_expect_success 'redirected clone -v does show progress' '
+
+ git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
+ >out 2>err &&
+ grep % err
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='some bundle related tests'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ test_commit initial &&
+ test_tick &&
+ git tag -m tag tag &&
+ test_commit second &&
+ test_commit third &&
+ git tag -d initial &&
+ git tag -d second &&
+ git tag -d third
+'
+
+test_expect_success 'annotated tags can be excluded by rev-list options' '
+ git bundle create bundle --all --since=7.Apr.2005.15:14:00.-0700 &&
+ git ls-remote bundle > output &&
+ grep tag output &&
+ git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
+ git ls-remote bundle > output &&
+ ! grep tag output
+'
+
+test_expect_success 'die if bundle file cannot be created' '
+ mkdir adir &&
+ test_must_fail git bundle create adir --all
+'
+
+test_expect_failure 'bundle --stdin' '
+ echo master | git bundle create stdin-bundle.bdl --stdin &&
+ git ls-remote stdin-bundle.bdl >output &&
+ grep master output
+'
+
+test_expect_failure 'bundle --stdin <rev-list options>' '
+ echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
+ git ls-remote hybrid-bundle.bdl >output &&
+ grep master output
+'
+
+test_expect_success 'empty bundle file is rejected' '
+ : >empty-bundle &&
+ test_must_fail git fetch empty-bundle
+'
+
+# This triggers a bug in older versions where the resulting line (with
+# --pretty=oneline) was longer than a 1024-char buffer.
+test_expect_success 'ridiculously long subject in boundary' '
+ : >file4 &&
+ test_tick &&
+ git add file4 &&
+ printf "%01200d\n" 0 | git commit -F - &&
+ test_commit fifth &&
+ git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
+ git bundle list-heads long-subject-bundle.bdl >heads &&
+ test -s heads &&
+ git fetch long-subject-bundle.bdl &&
+ sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
+ grep "^-[0-9a-f]\\{40\\} " boundary
+'
+
+test_expect_success 'prerequisites with an empty commit message' '
+ : >file1 &&
+ git add file1 &&
+ test_tick &&
+ git commit --allow-empty-message -m "" &&
+ test_commit file2 &&
+ git bundle create bundle HEAD^.. &&
+ git bundle verify bundle
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='Test cloning a repository larger than 2 gigabyte'
+. ./test-lib.sh
+
+if test -z "$GIT_TEST_CLONE_2GB"
+then
+ say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
+else
+ test_set_prereq CLONE_2GB
+fi
+
+test_expect_success CLONE_2GB 'setup' '
+
+ git config pack.compression 0 &&
+ git config pack.depth 0 &&
+ blobsize=$((100*1024*1024)) &&
+ blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
+ i=1 &&
+ (while test $i -le $blobcount
+ do
+ printf "Generating blob $i/$blobcount\r" >&2 &&
+ printf "blob\nmark :$i\ndata $blobsize\n" &&
+ #test-genrandom $i $blobsize &&
+ printf "%-${blobsize}s" $i &&
+ echo "M 100644 :$i $i" >> commit
+ i=$(($i+1)) ||
+ echo $? > exit-status
+ done &&
+ echo "commit refs/heads/master" &&
+ echo "author A U Thor <author@email.com> 123456789 +0000" &&
+ echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
+ echo "data 5" &&
+ echo ">2gb" &&
+ cat commit) |
+ git fast-import --big-file-threshold=2 &&
+ test ! -f exit-status
+
+'
+
+test_expect_success CLONE_2GB 'clone - bare' '
+
+ git clone --bare --no-hardlinks . clone-bare
+
+'
+
+test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
+
+ git clone "file://$(pwd)" clone-wt
+
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='clone --branch option'
+. ./test-lib.sh
+
+check_HEAD() {
+ echo refs/heads/"$1" >expect &&
+ git symbolic-ref HEAD >actual &&
+ test_cmp expect actual
+}
+
+check_file() {
+ echo "$1" >expect &&
+ test_cmp expect file
+}
+
+test_expect_success 'setup' '
+ mkdir parent &&
+ (cd parent && git init &&
+ echo one >file && git add file && git commit -m one &&
+ git checkout -b two &&
+ echo two >file && git add file && git commit -m two &&
+ git checkout master) &&
+ mkdir empty &&
+ (cd empty && git init)
+'
+
+test_expect_success 'vanilla clone chooses HEAD' '
+ git clone parent clone &&
+ (cd clone &&
+ check_HEAD master &&
+ check_file one
+ )
+'
+
+test_expect_success 'clone -b chooses specified branch' '
+ git clone -b two parent clone-two &&
+ (cd clone-two &&
+ check_HEAD two &&
+ check_file two
+ )
+'
+
+test_expect_success 'clone -b sets up tracking' '
+ (cd clone-two &&
+ echo origin >expect &&
+ git config branch.two.remote >actual &&
+ echo refs/heads/two >>expect &&
+ git config branch.two.merge >>actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
+ (cd clone-two &&
+ echo refs/remotes/origin/master >expect &&
+ git symbolic-ref refs/remotes/origin/HEAD >actual &&
+ test_cmp expect actual
+ )
+'
+
+test_expect_success 'clone -b with bogus branch' '
+ test_must_fail git clone -b bogus parent clone-bogus
+'
+
+test_expect_success 'clone -b not allowed with empty repos' '
+ test_must_fail git clone -b branch empty clone-branch-empty
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test cloning a repository with detached HEAD'
+. ./test-lib.sh
+
+head_is_detached() {
+ git --git-dir=$1/.git rev-parse --verify HEAD &&
+ test_must_fail git --git-dir=$1/.git symbolic-ref HEAD
+}
+
+test_expect_success 'setup' '
+ echo one >file &&
+ git add file &&
+ git commit -m one &&
+ echo two >file &&
+ git commit -a -m two &&
+ git tag two &&
+ echo three >file &&
+ git commit -a -m three
+'
+
+test_expect_success 'clone repo (detached HEAD points to branch)' '
+ git checkout master^0 &&
+ git clone "file://$PWD" detached-branch
+'
+test_expect_success 'cloned HEAD matches' '
+ echo three >expect &&
+ git --git-dir=detached-branch/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_failure 'cloned HEAD is detached' '
+ head_is_detached detached-branch
+'
+
+test_expect_success 'clone repo (detached HEAD points to tag)' '
+ git checkout two^0 &&
+ git clone "file://$PWD" detached-tag
+'
+test_expect_success 'cloned HEAD matches' '
+ echo two >expect &&
+ git --git-dir=detached-tag/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-tag
+'
+
+test_expect_success 'clone repo (detached HEAD points to history)' '
+ git checkout two^ &&
+ git clone "file://$PWD" detached-history
+'
+test_expect_success 'cloned HEAD matches' '
+ echo one >expect &&
+ git --git-dir=detached-history/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-history
+'
+
+test_expect_success 'clone repo (orphan detached HEAD)' '
+ git checkout master^0 &&
+ echo four >file &&
+ git commit -a -m four &&
+ git clone "file://$PWD" detached-orphan
+'
+test_expect_success 'cloned HEAD matches' '
+ echo four >expect &&
+ git --git-dir=detached-orphan/.git log -1 --format=%s >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'cloned HEAD is detached' '
+ head_is_detached detached-orphan
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='tests for git clone -c key=value'
+. ./test-lib.sh
+
+test_expect_success 'clone -c sets config in cloned repo' '
+ rm -rf child &&
+ git clone -c core.foo=bar . child &&
+ echo bar >expect &&
+ git --git-dir=child/.git config core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c can set multi-keys' '
+ rm -rf child &&
+ git clone -c core.foo=bar -c core.foo=baz . child &&
+ { echo bar; echo baz; } >expect &&
+ git --git-dir=child/.git config --get-all core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c without a value is boolean true' '
+ rm -rf child &&
+ git clone -c core.foo . child &&
+ echo true >expect &&
+ git --git-dir=child/.git config --bool core.foo >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'clone -c config is available during clone' '
+ echo content >file &&
+ git add file &&
+ git commit -m one &&
+ rm -rf child &&
+ git clone -c core.autocrlf . child &&
+ printf "content\\r\\n" >expect &&
+ test_cmp expect child/file
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test refspec written by clone-command'
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+ # Make two branches, "master" and "side"
+ echo one >file &&
+ git add file &&
+ git commit -m one &&
+ echo two >file &&
+ git commit -a -m two &&
+ git tag two &&
+ echo three >file &&
+ git commit -a -m three &&
+ git checkout -b side &&
+ echo four >file &&
+ git commit -a -m four &&
+ git checkout master &&
+
+ # default clone
+ git clone . dir_all &&
+
+ # default --single that follows HEAD=master
+ git clone --single-branch . dir_master &&
+
+ # default --single that follows HEAD=side
+ git checkout side &&
+ git clone --single-branch . dir_side &&
+
+ # explicit --single that follows side
+ git checkout master &&
+ git clone --single-branch --branch side . dir_side2 &&
+
+ # default --single with --mirror
+ git clone --single-branch --mirror . dir_mirror &&
+
+ # default --single with --branch and --mirror
+ git clone --single-branch --mirror --branch side . dir_mirror_side &&
+
+ # --single that does not know what branch to follow
+ git checkout two^ &&
+ git clone --single-branch . dir_detached &&
+
+ # explicit --single with tag
+ git clone --single-branch --branch two . dir_tag &&
+
+ # advance both "master" and "side" branches
+ git checkout side &&
+ echo five >file &&
+ git commit -a -m five &&
+ git checkout master &&
+ echo six >file &&
+ git commit -a -m six &&
+
+ # update tag
+ git tag -d two && git tag two
+'
+
+test_expect_success 'by default all branches will be kept updated' '
+ (
+ cd dir_all && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # follow both master and side
+ git for-each-ref refs/heads >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'by default no tags will be kept updated' '
+ (
+ cd dir_all && git fetch &&
+ git for-each-ref refs/tags >../actual
+ ) &&
+ git for-each-ref refs/tags >expect &&
+ test_must_fail test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at master' '
+ (
+ cd dir_master && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # only follow master
+ git for-each-ref refs/heads/master >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch while HEAD pointing at side' '
+ (
+ cd dir_side && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # only follow side
+ git for-each-ref refs/heads/side >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch side' '
+ (
+ cd dir_side2 && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # only follow side
+ git for-each-ref refs/heads/side >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' '
+ (
+ cd dir_tag && git fetch &&
+ git for-each-ref refs/tags >../actual
+ ) &&
+ git for-each-ref refs/tags >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with --mirror' '
+ (
+ cd dir_mirror && git fetch &&
+ git for-each-ref refs > ../actual
+ ) &&
+ git for-each-ref refs >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with explicit --branch and --mirror' '
+ (
+ cd dir_mirror_side && git fetch &&
+ git for-each-ref refs > ../actual
+ ) &&
+ git for-each-ref refs >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success '--single-branch with detached' '
+ (
+ cd dir_detached && git fetch &&
+ git for-each-ref refs/remotes/origin |
+ sed -e "/HEAD$/d" \
+ -e "s|/remotes/origin/|/heads/|" >../actual
+ ) &&
+ # nothing
+ >expect &&
+ test_cmp expect actual
+'
+
+test_done
--- /dev/null
+#!/bin/sh
+#
+# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
+#
+
+test_description='test transitive info/alternate entries'
+. ./test-lib.sh
+
+# test that a file is not reachable in the current repository
+# but that it is after creating a info/alternate entry
+reachable_via() {
+ alternate="$1"
+ file="$2"
+ if git cat-file -e "HEAD:$file"; then return 1; fi
+ echo "$alternate" >> .git/objects/info/alternate
+ git cat-file -e "HEAD:$file"
+}
+
+test_valid_repo() {
+ git fsck --full > fsck.log &&
+ test_line_count = 0 fsck.log
+}
+
+base_dir=$(pwd)
+
+test_expect_success 'preparing first repository' \
+'test_create_repo A && cd A &&
+echo "Hello World" > file1 &&
+git add file1 &&
+git commit -m "Initial commit" file1 &&
+git repack -a -d &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing second repository' \
+'git clone -l -s A B && cd B &&
+echo "foo bar" > file2 &&
+git add file2 &&
+git commit -m "next commit" file2 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'preparing third repository' \
+'git clone -l -s B C && cd C &&
+echo "Goodbye, cruel world" > file3 &&
+git add file3 &&
+git commit -m "one more" file3 &&
+git repack -a -d -l &&
+git prune'
+
+cd "$base_dir"
+
+test_expect_success 'creating too deep nesting' \
+'git clone -l -s C D &&
+git clone -l -s D E &&
+git clone -l -s E F &&
+git clone -l -s F G &&
+git clone --bare -l -s G H'
+
+test_expect_success 'invalidity of deepest repository' \
+'cd H && {
+ test_valid_repo
+ test $? -ne 0
+}'
+
+cd "$base_dir"
+
+test_expect_success 'validity of third repository' \
+'cd C &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'validity of fourth repository' \
+'cd D &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'breaking of loops' \
+'echo "$base_dir"/B/.git/objects >> "$base_dir"/A/.git/objects/info/alternates&&
+cd C &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success 'that info/alternates is necessary' \
+'cd C &&
+rm -f .git/objects/info/alternates &&
+! (test_valid_repo)'
+
+cd "$base_dir"
+
+test_expect_success 'that relative alternate is possible for current dir' \
+'cd C &&
+echo "../../../B/.git/objects" > .git/objects/info/alternates &&
+test_valid_repo'
+
+cd "$base_dir"
+
+test_expect_success \
+ 'that relative alternate is only possible for current dir' '
+ cd D &&
+ ! (test_valid_repo)
+'
+
+cd "$base_dir"
+
+test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
-#
-
-test_description='test clone --reference'
-. ./test-lib.sh
-
-base_dir=$(pwd)
-
-U=$base_dir/UPLOAD_LOG
-
-# create a commit in repo $1 with name $2
-commit_in () {
- (
- cd "$1" &&
- echo "$2" >"$2" &&
- git add "$2" &&
- git commit -m "$2"
- )
-}
-
-# check that there are $2 loose objects in repo $1
-test_objcount () {
- echo "$2" >expect &&
- git -C "$1" count-objects >actual.raw &&
- cut -d' ' -f1 <actual.raw >actual &&
- test_cmp expect actual
-}
-
-test_expect_success 'preparing first repository' '
- test_create_repo A &&
- commit_in A file1
-'
-
-test_expect_success 'preparing second repository' '
- git clone A B &&
- commit_in B file2 &&
- git -C B repack -ad &&
- git -C B prune
-'
-
-test_expect_success 'cloning with reference (-l -s)' '
- git clone -l -s --reference B A C
-'
-
-test_expect_success 'existence of info/alternates' '
- test_line_count = 2 C/.git/objects/info/alternates
-'
-
-test_expect_success 'pulling from reference' '
- git -C C pull ../B master
-'
-
-test_expect_success 'that reference gets used' '
- test_objcount C 0
-'
-
-test_expect_success 'cloning with reference (no -l -s)' '
- GIT_TRACE_PACKET=$U.D git clone --reference B "file://$(pwd)/A" D
-'
-
-test_expect_success 'fetched no objects' '
- test -s "$U.D" &&
- ! grep " want" "$U.D"
-'
-
-test_expect_success 'existence of info/alternates' '
- test_line_count = 1 D/.git/objects/info/alternates
-'
-
-test_expect_success 'pulling from reference' '
- git -C D pull ../B master
-'
-
-test_expect_success 'that reference gets used' '
- test_objcount D 0
-'
-
-test_expect_success 'updating origin' '
- commit_in A file3 &&
- git -C A repack -ad &&
- git -C A prune
-'
-
-test_expect_success 'pulling changes from origin' '
- git -C C pull origin
-'
-
-# the 2 local objects are commit and tree from the merge
-test_expect_success 'that alternate to origin gets used' '
- test_objcount C 2
-'
-
-test_expect_success 'pulling changes from origin' '
- git -C D pull origin
-'
-
-# the 5 local objects are expected; file3 blob, commit in A to add it
-# and its tree, and 2 are our tree and the merge commit.
-test_expect_success 'check objects expected to exist locally' '
- test_objcount D 5
-'
-
-test_expect_success 'preparing alternate repository #1' '
- test_create_repo F &&
- commit_in F file1
-'
-
-test_expect_success 'cloning alternate repo #2 and adding changes to repo #1' '
- git clone F G &&
- commit_in F file2
-'
-
-test_expect_success 'cloning alternate repo #1, using #2 as reference' '
- git clone --reference G F H
-'
-
-test_expect_success 'cloning with reference being subset of source (-l -s)' '
- git clone -l -s --reference A B E
-'
-
-test_expect_success 'cloning with multiple references drops duplicates' '
- git clone -s --reference B --reference A --reference B A dups &&
- test_line_count = 2 dups/.git/objects/info/alternates
-'
-
-test_expect_success 'clone with reference from a tagged repository' '
- (
- cd A && git tag -a -m tagged HEAD
- ) &&
- git clone --reference=A A I
-'
-
-test_expect_success 'prepare branched repository' '
- git clone A J &&
- (
- cd J &&
- git checkout -b other master^ &&
- echo other >otherfile &&
- git add otherfile &&
- git commit -m other &&
- git checkout master
- )
-'
-
-test_expect_success 'fetch with incomplete alternates' '
- git init K &&
- echo "$base_dir/A/.git/objects" >K/.git/objects/info/alternates &&
- (
- cd K &&
- git remote add J "file://$base_dir/J" &&
- GIT_TRACE_PACKET=$U.K git fetch J
- ) &&
- master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
- test -s "$U.K" &&
- ! grep " want $master_object" "$U.K" &&
- tag_object=$(cd A && git for-each-ref --format="%(objectname)" refs/tags/HEAD) &&
- ! grep " want $tag_object" "$U.K"
-'
-
-test_expect_success 'clone using repo with gitfile as a reference' '
- git clone --separate-git-dir=L A M &&
- git clone --reference=M A N &&
- echo "$base_dir/L/objects" >expected &&
- test_cmp expected "$base_dir/N/.git/objects/info/alternates"
-'
-
-test_expect_success 'clone using repo pointed at by gitfile as reference' '
- git clone --reference=M/.git A O &&
- echo "$base_dir/L/objects" >expected &&
- test_cmp expected "$base_dir/O/.git/objects/info/alternates"
-'
-
-test_expect_success 'clone and dissociate from reference' '
- git init P &&
- (
- cd P && test_commit one
- ) &&
- git clone P Q &&
- (
- cd Q && test_commit two
- ) &&
- git clone --no-local --reference=P Q R &&
- git clone --no-local --reference=P --dissociate Q S &&
- # removing the reference P would corrupt R but not S
- rm -fr P &&
- test_must_fail git -C R fsck &&
- git -C S fsck
-'
-test_expect_success 'clone, dissociate from partial reference and repack' '
- rm -fr P Q R &&
- git init P &&
- (
- cd P &&
- test_commit one &&
- git repack &&
- test_commit two &&
- git repack
- ) &&
- git clone --bare P Q &&
- (
- cd P &&
- git checkout -b second &&
- test_commit three &&
- git repack
- ) &&
- git clone --bare --dissociate --reference=P Q R &&
- ls R/objects/pack/*.pack >packs.txt &&
- test_line_count = 1 packs.txt
-'
-
-test_expect_success 'clone, dissociate from alternates' '
- rm -fr A B C &&
- test_create_repo A &&
- commit_in A file1 &&
- git clone --reference=A A B &&
- test_line_count = 1 B/.git/objects/info/alternates &&
- git clone --local --dissociate B C &&
- ! test -f C/.git/objects/info/alternates &&
- ( cd C && git fsck )
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test local clone'
-. ./test-lib.sh
-
-repo_is_hardlinked() {
- find "$1/objects" -type f -links 1 >output &&
- test_line_count = 0 output
-}
-
-test_expect_success 'preparing origin repository' '
- : >file && git add . && git commit -m1 &&
- git clone --bare . a.git &&
- git clone --bare . x &&
- test "$(cd a.git && git config --bool core.bare)" = true &&
- test "$(cd x && git config --bool core.bare)" = true &&
- git bundle create b1.bundle --all &&
- git bundle create b2.bundle master &&
- mkdir dir &&
- cp b1.bundle dir/b3 &&
- cp b1.bundle b4
-'
-
-test_expect_success 'local clone without .git suffix' '
- git clone -l -s a b &&
- (cd b &&
- test "$(git config --bool core.bare)" = false &&
- git fetch)
-'
-
-test_expect_success 'local clone with .git suffix' '
- git clone -l -s a.git c &&
- (cd c && git fetch)
-'
-
-test_expect_success 'local clone from x' '
- git clone -l -s x y &&
- (cd y && git fetch)
-'
-
-test_expect_success 'local clone from x.git that does not exist' '
- test_must_fail git clone -l -s x.git z
-'
-
-test_expect_success 'With -no-hardlinks, local will make a copy' '
- git clone --bare --no-hardlinks x w &&
- ! repo_is_hardlinked w
-'
-
-test_expect_success 'Even without -l, local will make a hardlink' '
- rm -fr w &&
- git clone -l --bare x w &&
- repo_is_hardlinked w
-'
-
-test_expect_success 'local clone of repo with nonexistent ref in HEAD' '
- echo "ref: refs/heads/nonexistent" > a.git/HEAD &&
- git clone a d &&
- (cd d &&
- git fetch &&
- test ! -e .git/refs/remotes/origin/HEAD)
-'
-
-test_expect_success 'bundle clone without .bundle suffix' '
- git clone dir/b3 &&
- (cd b3 && git fetch)
-'
-
-test_expect_success 'bundle clone with .bundle suffix' '
- git clone b1.bundle &&
- (cd b1 && git fetch)
-'
-
-test_expect_success 'bundle clone from b4' '
- git clone b4 bdl &&
- (cd bdl && git fetch)
-'
-
-test_expect_success 'bundle clone from b4.bundle that does not exist' '
- test_must_fail git clone b4.bundle bb
-'
-
-test_expect_success 'bundle clone with nonexistent HEAD' '
- git clone b2.bundle b2 &&
- (cd b2 &&
- git fetch &&
- test_must_fail git rev-parse --verify refs/heads/master)
-'
-
-test_expect_success 'clone empty repository' '
- mkdir empty &&
- (cd empty &&
- git init &&
- git config receive.denyCurrentBranch warn) &&
- git clone empty empty-clone &&
- test_tick &&
- (cd empty-clone
- echo "content" >> foo &&
- git add foo &&
- git commit -m "Initial commit" &&
- git push origin master &&
- expected=$(git rev-parse master) &&
- actual=$(git --git-dir=../empty/.git rev-parse master) &&
- test $actual = $expected)
-'
-
-test_expect_success 'clone empty repository, and then push should not segfault.' '
- rm -fr empty/ empty-clone/ &&
- mkdir empty &&
- (cd empty && git init) &&
- git clone empty empty-clone &&
- (cd empty-clone &&
- test_must_fail git push)
-'
-
-test_expect_success 'cloning non-existent directory fails' '
- rm -rf does-not-exist &&
- test_must_fail git clone does-not-exist
-'
-
-test_expect_success 'cloning non-git directory fails' '
- rm -rf not-a-git-repo not-a-git-repo-clone &&
- mkdir not-a-git-repo &&
- test_must_fail git clone not-a-git-repo not-a-git-repo-clone
-'
-
-test_expect_success 'cloning file:// does not hardlink' '
- git clone --bare file://"$(pwd)"/a non-local &&
- ! repo_is_hardlinked non-local
-'
-
-test_expect_success 'cloning a local path with --no-local does not hardlink' '
- git clone --bare --no-local a force-nonlocal &&
- ! repo_is_hardlinked force-nonlocal
-'
-
-test_expect_success 'cloning locally respects "-u" for fetching refs' '
- test_must_fail git clone --bare -u false a should_not_work.git
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='basic clone options'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
-
- mkdir parent &&
- (cd parent && git init &&
- echo one >file && git add file &&
- git commit -m one)
-
-'
-
-test_expect_success 'clone -o' '
-
- git clone -o foo parent clone-o &&
- (cd clone-o && git rev-parse --verify refs/remotes/foo/master)
-
-'
-
-test_expect_success 'redirected clone does not show progress' '
-
- git clone "file://$(pwd)/parent" clone-redirected >out 2>err &&
- ! grep % err &&
- test_i18ngrep ! "Checking connectivity" err
-
-'
-
-test_expect_success 'redirected clone -v does show progress' '
-
- git clone --progress "file://$(pwd)/parent" clone-redirected-progress \
- >out 2>err &&
- grep % err
-
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='some bundle related tests'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
- test_commit initial &&
- test_tick &&
- git tag -m tag tag &&
- test_commit second &&
- test_commit third &&
- git tag -d initial &&
- git tag -d second &&
- git tag -d third
-'
-
-test_expect_success 'annotated tags can be excluded by rev-list options' '
- git bundle create bundle --all --since=7.Apr.2005.15:14:00.-0700 &&
- git ls-remote bundle > output &&
- grep tag output &&
- git bundle create bundle --all --since=7.Apr.2005.15:16:00.-0700 &&
- git ls-remote bundle > output &&
- ! grep tag output
-'
-
-test_expect_success 'die if bundle file cannot be created' '
- mkdir adir &&
- test_must_fail git bundle create adir --all
-'
-
-test_expect_failure 'bundle --stdin' '
- echo master | git bundle create stdin-bundle.bdl --stdin &&
- git ls-remote stdin-bundle.bdl >output &&
- grep master output
-'
-
-test_expect_failure 'bundle --stdin <rev-list options>' '
- echo master | git bundle create hybrid-bundle.bdl --stdin tag &&
- git ls-remote hybrid-bundle.bdl >output &&
- grep master output
-'
-
-test_expect_success 'empty bundle file is rejected' '
- : >empty-bundle &&
- test_must_fail git fetch empty-bundle
-'
-
-# This triggers a bug in older versions where the resulting line (with
-# --pretty=oneline) was longer than a 1024-char buffer.
-test_expect_success 'ridiculously long subject in boundary' '
- : >file4 &&
- test_tick &&
- git add file4 &&
- printf "%01200d\n" 0 | git commit -F - &&
- test_commit fifth &&
- git bundle create long-subject-bundle.bdl HEAD^..HEAD &&
- git bundle list-heads long-subject-bundle.bdl >heads &&
- test -s heads &&
- git fetch long-subject-bundle.bdl &&
- sed -n "/^-/{p;q;}" long-subject-bundle.bdl >boundary &&
- grep "^-[0-9a-f]\\{40\\} " boundary
-'
-
-test_expect_success 'prerequisites with an empty commit message' '
- : >file1 &&
- git add file1 &&
- test_tick &&
- git commit --allow-empty-message -m "" &&
- test_commit file2 &&
- git bundle create bundle HEAD^.. &&
- git bundle verify bundle
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='Test cloning a repository larger than 2 gigabyte'
-. ./test-lib.sh
-
-if test -z "$GIT_TEST_CLONE_2GB"
-then
- say 'Skipping expensive 2GB clone test; enable it with GIT_TEST_CLONE_2GB=t'
-else
- test_set_prereq CLONE_2GB
-fi
-
-test_expect_success CLONE_2GB 'setup' '
-
- git config pack.compression 0 &&
- git config pack.depth 0 &&
- blobsize=$((100*1024*1024)) &&
- blobcount=$((2*1024*1024*1024/$blobsize+1)) &&
- i=1 &&
- (while test $i -le $blobcount
- do
- printf "Generating blob $i/$blobcount\r" >&2 &&
- printf "blob\nmark :$i\ndata $blobsize\n" &&
- #test-genrandom $i $blobsize &&
- printf "%-${blobsize}s" $i &&
- echo "M 100644 :$i $i" >> commit
- i=$(($i+1)) ||
- echo $? > exit-status
- done &&
- echo "commit refs/heads/master" &&
- echo "author A U Thor <author@email.com> 123456789 +0000" &&
- echo "committer C O Mitter <committer@email.com> 123456789 +0000" &&
- echo "data 5" &&
- echo ">2gb" &&
- cat commit) |
- git fast-import --big-file-threshold=2 &&
- test ! -f exit-status
-
-'
-
-test_expect_success CLONE_2GB 'clone - bare' '
-
- git clone --bare --no-hardlinks . clone-bare
-
-'
-
-test_expect_success CLONE_2GB 'clone - with worktree, file:// protocol' '
-
- git clone "file://$(pwd)" clone-wt
-
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='clone --branch option'
-. ./test-lib.sh
-
-check_HEAD() {
- echo refs/heads/"$1" >expect &&
- git symbolic-ref HEAD >actual &&
- test_cmp expect actual
-}
-
-check_file() {
- echo "$1" >expect &&
- test_cmp expect file
-}
-
-test_expect_success 'setup' '
- mkdir parent &&
- (cd parent && git init &&
- echo one >file && git add file && git commit -m one &&
- git checkout -b two &&
- echo two >file && git add file && git commit -m two &&
- git checkout master) &&
- mkdir empty &&
- (cd empty && git init)
-'
-
-test_expect_success 'vanilla clone chooses HEAD' '
- git clone parent clone &&
- (cd clone &&
- check_HEAD master &&
- check_file one
- )
-'
-
-test_expect_success 'clone -b chooses specified branch' '
- git clone -b two parent clone-two &&
- (cd clone-two &&
- check_HEAD two &&
- check_file two
- )
-'
-
-test_expect_success 'clone -b sets up tracking' '
- (cd clone-two &&
- echo origin >expect &&
- git config branch.two.remote >actual &&
- echo refs/heads/two >>expect &&
- git config branch.two.merge >>actual &&
- test_cmp expect actual
- )
-'
-
-test_expect_success 'clone -b does not munge remotes/origin/HEAD' '
- (cd clone-two &&
- echo refs/remotes/origin/master >expect &&
- git symbolic-ref refs/remotes/origin/HEAD >actual &&
- test_cmp expect actual
- )
-'
-
-test_expect_success 'clone -b with bogus branch' '
- test_must_fail git clone -b bogus parent clone-bogus
-'
-
-test_expect_success 'clone -b not allowed with empty repos' '
- test_must_fail git clone -b branch empty clone-branch-empty
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test cloning a repository with detached HEAD'
-. ./test-lib.sh
-
-head_is_detached() {
- git --git-dir=$1/.git rev-parse --verify HEAD &&
- test_must_fail git --git-dir=$1/.git symbolic-ref HEAD
-}
-
-test_expect_success 'setup' '
- echo one >file &&
- git add file &&
- git commit -m one &&
- echo two >file &&
- git commit -a -m two &&
- git tag two &&
- echo three >file &&
- git commit -a -m three
-'
-
-test_expect_success 'clone repo (detached HEAD points to branch)' '
- git checkout master^0 &&
- git clone "file://$PWD" detached-branch
-'
-test_expect_success 'cloned HEAD matches' '
- echo three >expect &&
- git --git-dir=detached-branch/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_failure 'cloned HEAD is detached' '
- head_is_detached detached-branch
-'
-
-test_expect_success 'clone repo (detached HEAD points to tag)' '
- git checkout two^0 &&
- git clone "file://$PWD" detached-tag
-'
-test_expect_success 'cloned HEAD matches' '
- echo two >expect &&
- git --git-dir=detached-tag/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_success 'cloned HEAD is detached' '
- head_is_detached detached-tag
-'
-
-test_expect_success 'clone repo (detached HEAD points to history)' '
- git checkout two^ &&
- git clone "file://$PWD" detached-history
-'
-test_expect_success 'cloned HEAD matches' '
- echo one >expect &&
- git --git-dir=detached-history/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_success 'cloned HEAD is detached' '
- head_is_detached detached-history
-'
-
-test_expect_success 'clone repo (orphan detached HEAD)' '
- git checkout master^0 &&
- echo four >file &&
- git commit -a -m four &&
- git clone "file://$PWD" detached-orphan
-'
-test_expect_success 'cloned HEAD matches' '
- echo four >expect &&
- git --git-dir=detached-orphan/.git log -1 --format=%s >actual &&
- test_cmp expect actual
-'
-test_expect_success 'cloned HEAD is detached' '
- head_is_detached detached-orphan
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='tests for git clone -c key=value'
-. ./test-lib.sh
-
-test_expect_success 'clone -c sets config in cloned repo' '
- rm -rf child &&
- git clone -c core.foo=bar . child &&
- echo bar >expect &&
- git --git-dir=child/.git config core.foo >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'clone -c can set multi-keys' '
- rm -rf child &&
- git clone -c core.foo=bar -c core.foo=baz . child &&
- { echo bar; echo baz; } >expect &&
- git --git-dir=child/.git config --get-all core.foo >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'clone -c without a value is boolean true' '
- rm -rf child &&
- git clone -c core.foo . child &&
- echo true >expect &&
- git --git-dir=child/.git config --bool core.foo >actual &&
- test_cmp expect actual
-'
-
-test_expect_success 'clone -c config is available during clone' '
- echo content >file &&
- git add file &&
- git commit -m one &&
- rm -rf child &&
- git clone -c core.autocrlf . child &&
- printf "content\\r\\n" >expect &&
- test_cmp expect child/file
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test refspec written by clone-command'
-. ./test-lib.sh
-
-test_expect_success 'setup' '
- # Make two branches, "master" and "side"
- echo one >file &&
- git add file &&
- git commit -m one &&
- echo two >file &&
- git commit -a -m two &&
- git tag two &&
- echo three >file &&
- git commit -a -m three &&
- git checkout -b side &&
- echo four >file &&
- git commit -a -m four &&
- git checkout master &&
-
- # default clone
- git clone . dir_all &&
-
- # default --single that follows HEAD=master
- git clone --single-branch . dir_master &&
-
- # default --single that follows HEAD=side
- git checkout side &&
- git clone --single-branch . dir_side &&
-
- # explicit --single that follows side
- git checkout master &&
- git clone --single-branch --branch side . dir_side2 &&
-
- # default --single with --mirror
- git clone --single-branch --mirror . dir_mirror &&
-
- # default --single with --branch and --mirror
- git clone --single-branch --mirror --branch side . dir_mirror_side &&
-
- # --single that does not know what branch to follow
- git checkout two^ &&
- git clone --single-branch . dir_detached &&
-
- # explicit --single with tag
- git clone --single-branch --branch two . dir_tag &&
-
- # advance both "master" and "side" branches
- git checkout side &&
- echo five >file &&
- git commit -a -m five &&
- git checkout master &&
- echo six >file &&
- git commit -a -m six &&
-
- # update tag
- git tag -d two && git tag two
-'
-
-test_expect_success 'by default all branches will be kept updated' '
- (
- cd dir_all && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # follow both master and side
- git for-each-ref refs/heads >expect &&
- test_cmp expect actual
-'
-
-test_expect_success 'by default no tags will be kept updated' '
- (
- cd dir_all && git fetch &&
- git for-each-ref refs/tags >../actual
- ) &&
- git for-each-ref refs/tags >expect &&
- test_must_fail test_cmp expect actual
-'
-
-test_expect_success '--single-branch while HEAD pointing at master' '
- (
- cd dir_master && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # only follow master
- git for-each-ref refs/heads/master >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch while HEAD pointing at side' '
- (
- cd dir_side && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # only follow side
- git for-each-ref refs/heads/side >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with explicit --branch side' '
- (
- cd dir_side2 && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # only follow side
- git for-each-ref refs/heads/side >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with explicit --branch with tag fetches updated tag' '
- (
- cd dir_tag && git fetch &&
- git for-each-ref refs/tags >../actual
- ) &&
- git for-each-ref refs/tags >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with --mirror' '
- (
- cd dir_mirror && git fetch &&
- git for-each-ref refs > ../actual
- ) &&
- git for-each-ref refs >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with explicit --branch and --mirror' '
- (
- cd dir_mirror_side && git fetch &&
- git for-each-ref refs > ../actual
- ) &&
- git for-each-ref refs >expect &&
- test_cmp expect actual
-'
-
-test_expect_success '--single-branch with detached' '
- (
- cd dir_detached && git fetch &&
- git for-each-ref refs/remotes/origin |
- sed -e "/HEAD$/d" \
- -e "s|/remotes/origin/|/heads/|" >../actual
- ) &&
- # nothing
- >expect &&
- test_cmp expect actual
-'
-
-test_done
+++ /dev/null
-#!/bin/sh
-#
-# Copyright (C) 2006 Martin Waitz <tali@admingilde.org>
-#
-
-test_description='test transitive info/alternate entries'
-. ./test-lib.sh
-
-# test that a file is not reachable in the current repository
-# but that it is after creating a info/alternate entry
-reachable_via() {
- alternate="$1"
- file="$2"
- if git cat-file -e "HEAD:$file"; then return 1; fi
- echo "$alternate" >> .git/objects/info/alternate
- git cat-file -e "HEAD:$file"
-}
-
-test_valid_repo() {
- git fsck --full > fsck.log &&
- test_line_count = 0 fsck.log
-}
-
-base_dir=$(pwd)
-
-test_expect_success 'preparing first repository' \
-'test_create_repo A && cd A &&
-echo "Hello World" > file1 &&
-git add file1 &&
-git commit -m "Initial commit" file1 &&
-git repack -a -d &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'preparing second repository' \
-'git clone -l -s A B && cd B &&
-echo "foo bar" > file2 &&
-git add file2 &&
-git commit -m "next commit" file2 &&
-git repack -a -d -l &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'preparing third repository' \
-'git clone -l -s B C && cd C &&
-echo "Goodbye, cruel world" > file3 &&
-git add file3 &&
-git commit -m "one more" file3 &&
-git repack -a -d -l &&
-git prune'
-
-cd "$base_dir"
-
-test_expect_success 'creating too deep nesting' \
-'git clone -l -s C D &&
-git clone -l -s D E &&
-git clone -l -s E F &&
-git clone -l -s F G &&
-git clone --bare -l -s G H'
-
-test_expect_success 'invalidity of deepest repository' \
-'cd H && {
- test_valid_repo
- test $? -ne 0
-}'
-
-cd "$base_dir"
-
-test_expect_success 'validity of third repository' \
-'cd C &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success 'validity of fourth repository' \
-'cd D &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success 'breaking of loops' \
-'echo "$base_dir"/B/.git/objects >> "$base_dir"/A/.git/objects/info/alternates&&
-cd C &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success 'that info/alternates is necessary' \
-'cd C &&
-rm -f .git/objects/info/alternates &&
-! (test_valid_repo)'
-
-cd "$base_dir"
-
-test_expect_success 'that relative alternate is possible for current dir' \
-'cd C &&
-echo "../../../B/.git/objects" > .git/objects/info/alternates &&
-test_valid_repo'
-
-cd "$base_dir"
-
-test_expect_success \
- 'that relative alternate is only possible for current dir' '
- cd D &&
- ! (test_valid_repo)
-'
-
-cd "$base_dir"
-
-test_done
)
'
+test_expect_success 'recursive relative submodules stay relative' '
+ test_when_finished "rm -rf super clone2 subsub sub3" &&
+ mkdir subsub &&
+ (
+ cd subsub &&
+ git init &&
+ >t &&
+ git add t &&
+ git commit -m "initial commit"
+ ) &&
+ mkdir sub3 &&
+ (
+ cd sub3 &&
+ git init &&
+ >t &&
+ git add t &&
+ git commit -m "initial commit" &&
+ git submodule add ../subsub dirdir/subsub &&
+ git commit -m "add submodule subsub"
+ ) &&
+ mkdir super &&
+ (
+ cd super &&
+ git init &&
+ >t &&
+ git add t &&
+ git commit -m "initial commit" &&
+ git submodule add ../sub3 &&
+ git commit -m "add submodule sub"
+ ) &&
+ git clone super clone2 &&
+ (
+ cd clone2 &&
+ git submodule update --init --recursive &&
+ echo "gitdir: ../.git/modules/sub3" >./sub3/.git_expect &&
+ echo "gitdir: ../../../.git/modules/sub3/modules/dirdir/subsub" >./sub3/dirdir/subsub/.git_expect
+ ) &&
+ test_cmp clone2/sub3/.git_expect clone2/sub3/.git &&
+ test_cmp clone2/sub3/dirdir/subsub/.git_expect clone2/sub3/dirdir/subsub/.git
+'
+
test_expect_success 'submodule add with an existing name fails unless forced' '
(
cd addtest2 &&
test_cmp expect msg
'
+test_expect_success '--amend to set message to empty' '
+ echo bata >file &&
+ git add file &&
+ git commit -m "unamended" &&
+ git commit --amend --allow-empty-message -m "" &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ echo "" >expect &&
+ test_cmp expect msg
+'
+
+test_expect_success '--amend to set empty message needs --allow-empty-message' '
+ echo conga >file &&
+ git add file &&
+ git commit -m "unamended" &&
+ test_must_fail git commit --amend -m "" &&
+ git diff-tree -s --format=%s HEAD >msg &&
+ echo "unamended" >expect &&
+ test_cmp expect msg
+'
+
test_expect_success '-m --edit' '
echo amended >expect &&
git commit --allow-empty -m buffer &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
- test_expect_success 'commit' '
- try_commit "" &&
- test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
- '
-
test_expect_success 'commit --status' '
try_commit --status &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >file.1
printf '%s\n' 1 2 3 4 '5 X' 6 7 8 9 >file.5
printf '%s\n' 1 2 3 4 5 6 7 8 '9 X' >file.9
+printf '%s\n' 1 2 3 4 5 6 7 8 '9 Y' >file.9y
printf '%s\n' '1 X' 2 3 4 5 6 7 8 9 >result.1
printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 9 >result.1-5
printf '%s\n' '1 X' 2 3 4 '5 X' 6 7 8 '9 X' >result.1-5-9
+printf '%s\n' 1 2 3 4 5 6 7 8 '9 Z' >result.9z
>empty
create_merge_msgs () {
git tag c2 &&
c2=$(git rev-parse HEAD) &&
git reset --hard "$c0" &&
+ cp file.9y file &&
+ git add file &&
+ test_tick &&
+ git commit -m "commit 7" &&
+ git tag c7 &&
+ git reset --hard "$c0" &&
cp file.9 file &&
git add file &&
test_tick &&
verify_parents $c1 $c2
'
+test_expect_success 'merge --squash c3 with c7' '
+ git reset --hard c3 &&
+ test_must_fail git merge --squash c7 &&
+ cat result.9z >file &&
+ git commit --no-edit -a &&
+
+ {
+ cat <<-EOF
+ Squashed commit of the following:
+
+ $(git show -s c7)
+
+ # Conflicts:
+ # file
+ EOF
+ } >expect &&
+ git cat-file commit HEAD | sed -e '1,/^$/d' >actual &&
+ test_cmp expect actual
+'
+
test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 and c3' '
test_must_fail git merge -s resolve master
'
+test_expect_success 'merge nothing into void' '
+ git init void &&
+ (
+ cd void &&
+ git remote add up .. &&
+ git fetch up &&
+ test_must_fail git merge FETCH_HEAD
+ )
+'
+
test_done
git reset --hard
'
+test_expect_success 'mergetool delete/delete conflict' '
+ git checkout -b delete-base branch1 &&
+ mkdir -p a/a &&
+ (echo one; echo two; echo 3; echo 4) >a/a/file.txt &&
+ git add a/a/file.txt &&
+ git commit -m"base file" &&
+ git checkout -b move-to-b delete-base &&
+ mkdir -p b/b &&
+ git mv a/a/file.txt b/b/file.txt &&
+ (echo one; echo two; echo 4) >b/b/file.txt &&
+ git commit -a -m"move to b" &&
+ git checkout -b move-to-c delete-base &&
+ mkdir -p c/c &&
+ git mv a/a/file.txt c/c/file.txt &&
+ (echo one; echo two; echo 3) >c/c/file.txt &&
+ git commit -a -m"move to c" &&
+ test_must_fail git merge move-to-b &&
+ echo d | git mergetool a/a/file.txt &&
+ ! test -f a/a/file.txt &&
+ git reset --hard HEAD &&
+ test_must_fail git merge move-to-b &&
+ echo m | git mergetool a/a/file.txt &&
+ test -f b/b/file.txt &&
+ git reset --hard HEAD &&
+ test_must_fail git merge move-to-b &&
+ ! echo a | git mergetool a/a/file.txt &&
+ ! test -f a/a/file.txt &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'mergetool produces no errors when keepBackup is used' '
+ test_config mergetool.keepBackup true &&
+ test_must_fail git merge move-to-b &&
+ : >expect &&
+ echo d | git mergetool a/a/file.txt 2>actual &&
+ test_cmp expect actual &&
+ ! test -d a &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'mergetool honors tempfile config for deleted files' '
+ test_config mergetool.keepTemporaries false &&
+ test_must_fail git merge move-to-b &&
+ echo d | git mergetool a/a/file.txt &&
+ ! test -d a &&
+ git reset --hard HEAD
+'
+
+test_expect_success 'mergetool keeps tempfiles when aborting delete/delete' '
+ test_config mergetool.keepTemporaries true &&
+ test_must_fail git merge move-to-b &&
+ ! (echo a; echo n) | git mergetool a/a/file.txt &&
+ test -d a/a &&
+ cat >expect <<-\EOF &&
+ file_BASE_.txt
+ file_LOCAL_.txt
+ file_REMOTE_.txt
+ EOF
+ ls -1 a/a | sed -e "s/[0-9]*//g" >actual &&
+ test_cmp expect actual &&
+ git clean -fdx &&
+ git reset --hard HEAD
+'
+
test_expect_success 'deleted vs modified submodule' '
git checkout -b test6 branch1 &&
git submodule update -N &&
)
'
+test_expect_success 'grep --no-index descends into repos, but not .git' '
+ rm -fr non &&
+ mkdir -p non/git &&
+ (
+ GIT_CEILING_DIRECTORIES="$(pwd)/non" &&
+ export GIT_CEILING_DIRECTORIES &&
+ cd non/git &&
+
+ echo magic >file &&
+ git init repo &&
+ (
+ cd repo &&
+ echo magic >file &&
+ git add file &&
+ git commit -m foo &&
+ echo magic >.git/file
+ ) &&
+
+ cat >expect <<-\EOF &&
+ file
+ repo/file
+ EOF
+ git grep -l --no-index magic >actual &&
+ test_cmp expect actual
+ )
+'
+
test_expect_success 'setup double-dash tests' '
cat >double-dash <<EOF &&
--
grep "A U Thor" actual
'
+test_expect_success 'blame file with CRLF core.autocrlf=true' '
+ git config core.autocrlf false &&
+ printf "testcase\r\n" >crlfinrepo &&
+ >.gitattributes &&
+ git add crlfinrepo &&
+ git commit -m "add crlfinrepo" &&
+ git config core.autocrlf true &&
+ mv crlfinrepo tmp &&
+ git checkout crlfinrepo &&
+ rm tmp &&
+ git blame crlfinrepo >actual &&
+ grep "A U Thor" actual
+'
+
test_done
git fast-import </dev/null
'
+test_expect_success 'truncated stream complains' '
+ echo "tag foo" | test_must_fail git fast-import
+'
+
test_expect_success 'A: create pack from stdin' '
test_tick &&
cat >input <<-INPUT_END &&
unsigned char hash1[20], hash2[20], shifted[20];
struct tree *one, *two;
+ setup_git_directory();
+
if (get_sha1(av[1], hash1))
die("cannot parse %s as an object name", av[1]);
if (get_sha1(av[2], hash2))
if (argc < 2)
return 1;
+ setup_git_directory();
+
if (!strcmp(argv[1], "run-twice")) {
printf("1st\n");
if (!run_revision_walk())
"corruption on the remote side.";
int buffered = -1;
ssize_t sz;
- const char *argv[13];
- int i, arg = 0;
+ int i;
FILE *pipe_fd;
if (shallow_nr) {
- argv[arg++] = "--shallow-file";
- argv[arg++] = "";
+ argv_array_push(&pack_objects.args, "--shallow-file");
+ argv_array_push(&pack_objects.args, "");
}
- argv[arg++] = "pack-objects";
- argv[arg++] = "--revs";
+ argv_array_push(&pack_objects.args, "pack-objects");
+ argv_array_push(&pack_objects.args, "--revs");
if (use_thin_pack)
- argv[arg++] = "--thin";
+ argv_array_push(&pack_objects.args, "--thin");
- argv[arg++] = "--stdout";
+ argv_array_push(&pack_objects.args, "--stdout");
if (shallow_nr)
- argv[arg++] = "--shallow";
+ argv_array_push(&pack_objects.args, "--shallow");
if (!no_progress)
- argv[arg++] = "--progress";
+ argv_array_push(&pack_objects.args, "--progress");
if (use_ofs_delta)
- argv[arg++] = "--delta-base-offset";
+ argv_array_push(&pack_objects.args, "--delta-base-offset");
if (use_include_tag)
- argv[arg++] = "--include-tag";
- argv[arg++] = NULL;
+ argv_array_push(&pack_objects.args, "--include-tag");
pack_objects.in = -1;
pack_objects.out = -1;
pack_objects.err = -1;
pack_objects.git_cmd = 1;
- pack_objects.argv = argv;
if (start_command(&pack_objects))
die("git upload-pack: unable to fork git-pack-objects");
xdl_free_ctx(&xe->xdf2);
xdl_free_ctx(&xe->xdf1);
+ xdl_free_classifier(&cf);
return -1;
}
- if (!(xpp->flags & XDF_HISTOGRAM_DIFF))
+ if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)
xdl_free_classifier(&cf);
return 0;