Writing Documentation:
+ Most (if not all) of the documentation pages are written in AsciiDoc
+ and processed into HTML output and manpages.
+
Every user-visible change should be reflected in the documentation.
The same general rule as for code applies -- imitate the existing
conventions. A few commented examples follow to provide reference
--- /dev/null
+Git v1.8.2.1 Release Notes
+==========================
+
+Fixes since v1.8.2
+------------------
+
+ * The logic used by "git diff -M --stat" to shorten the names of
+ files before and after a rename did not work correctly when the
+ common prefix and suffix between the two filenames overlapped.
+
+ * "git submodule update", when recursed into sub-submodules, did not
+ acccumulate the prefix paths.
+
+ * "git am $maildir/" applied messages in an unexpected order; sort
+ filenames read from the maildir/ in a way that is more likely to
+ sort messages in the order the writing MUA meant to, by sorting
+ numeric segment in numeric order and non-numeric segment in
+ alphabetical order.
+
+ * When export-subst is used, "zip" output recorded incorrect
+ size of the file.
+
+ * Some platforms and users spell UTF-8 differently; retry with the
+ most official "UTF-8" when the system does not understand the
+ user-supplied encoding name that are the common alternative
+ spellings of UTF-8.
+
+ * "git branch" did not bother to check nonsense command line
+ parameters and issue errors in many cases.
+
+ * "git update-index -h" did not do the usual "-h(elp)" thing.
+
+ * perl/Git.pm::cat_blob slurped everything in core only to write it
+ out to a file descriptor, which was not a very smart thing to do.
+
+ * The SSL peer verification done by "git imap-send" did not ask for
+ Server Name Indication (RFC 4366), failing to connect SSL/TLS
+ sites that serve multiple hostnames on a single IP.
+
+ * "git index-pack" had a buffer-overflow while preparing an
+ informational message when the translated version of it was too
+ long.
+
+ * Clarify in the documentation "what" gets pushed to "where" when the
+ command line to "git push" does not say these explicitly.
+
+ * In "git reflog expire", REACHABLE bit was not cleared from the
+ correct objects.
+
+ * The "--color=<when>" argument to the commands in the diff family
+ was described poorly.
+
+ * The arguments given to pre-rebase hook were not documented.
+
+ * The v4 index format was not documented.
+
+ * The "--match=<pattern>" argument "git describe" takes uses glob
+ pattern but it wasn't obvious from the documentation.
+
+ * Some sources failed to compile on systems that lack NI_MAXHOST in
+ their system header (e.g. z/OS).
+
+ * Add an example use of "--env-filter" in "filter-branch"
+ documentation.
+
+ * "git bundle verify" did not say "records a complete history" for a
+ bundle that does not have any prerequisites.
+
+ * In the v1.8.0 era, we changed symbols that do not have to be global
+ to file scope static, but a few functions in graph.c were used by
+ CGit from sideways bypassing the entry points of the API the
+ in-tree users use.
+
+ * "git merge-tree" had a typo in the logic to detect d/f conflicts,
+ which caused it to segfault in some cases.
An explicit way to help the end users who connect to the service by
issuing custom messages to refuse such an access has been added.
- * "git status" suggests users to look into using--untracked=no option
+ * In addition to the case where the user edits the log message with
+ the "e)dit" option of "am -i", replace the "Applying: this patch"
+ message with the final log message contents after applymsg hook
+ munges it.
+
+ * "git status" suggests users to look into using --untracked=no option
when it takes too long.
+ * "git status" shows a bit more information to "git status" during a
+ rebase/bisect session.
+
* "git fetch" learned to fetch a commit at the tip of an unadvertised
ref by specifying a raw object name from the command line when the
server side supports this feature.
* "git send-email" can be used with the credential helper system.
+ * There was no Porcelain way to say "I no longer am interested in
+ this submodule", once you express your interest in a submodule with
+ "submodule init". "submodule deinit" is the way to do so.
+
+ * "git pull --rebase" learned to pass "-v/-q" options to underlying
+ "git rebase".
-Foreign Interface
+ * The new "--follow-tags" option tells "git push" to push relevant
+ annotated tags when pushing branches out.
+ * "git mergetool" now feeds files to the "p4merge" backend in the
+ order that matches the p4 convention, where "theirs" is usually
+ shown on the left side, which is the opposite from other backend
+ expects.
+
+ * "show/log" now honors gpg.program configuration just like other
+ parts of the code that use GnuPG.
+
+ * "git log" that shows the difference between the parent and the
+ child has been optimized somewhat.
Performance, Internal Implementation, etc.
hashtables they use upfront, but still started from a small table
repeatedly growing and rehashing.
+ * The API to walk reflog entries from the latest to older, which was
+ necessary for operations such as "git checkout -", was cumbersome
+ to use correctly and also inefficient.
+
+ * Codepaths that inspect log-message-to-be and decide when to add a
+ new Signed-off-by line in various commands have been consolidated.
+
+ * The pkt-line API, implementation and its callers have been cleaned
+ up to make them more robust.
+
+ * Cygwin port has a faster-but-lying lstat(2) emulation whose
+ incorrectness does not matter in practice except for a few
+ codepaths, and setting permission bits to directories is a codepath
+ that needs to use a more correct one.
+
Also contains minor documentation updates and code clean-ups.
track are contained in this release (see release notes to them for
details).
+ * "index-pack --fix-thin" used uninitialize value to compute delta
+ depths of objects it appends to the resulting pack.
+ (merge 57165db jk/index-pack-correct-depth-fix later to maint).
+
+ * "index-pack --verify-stat" used a few counters outside protection
+ of mutex, possibly showing incorrect numbers.
+ (merge 8f82aad nd/index-pack-threaded-fixes later to maint).
+
+ * The code to keep track of what directory names are known to Git on
+ platforms with case insensitive filesystems can get confused upon a
+ hash collision between these pathnames and looped forever.
+ (merge 2092678 kb/name-hash later to maint).
+
+ * Annotated tags outside refs/tags/ hierarchy were not advertised
+ correctly to the ls-remote and fetch with recent version of Git.
+ (merge c29c46f jk/fully-peeled-packed-ref later to maint).
+
+ * Recent optimization broke shallow clones.
+ (merge f59de5d jk/peel-ref later to maint).
+
+ * "git cmd -- ':(top'" was not diagnosed as an invalid syntax, and
+ instead the parser kept reading beyond the end of the string.
+ (merge f612a67 lf/setup-prefix-pathspec later to maint).
+
+ * "git tag -f <tag>" always said "Updated tag '<tag>'" even when
+ creating a new tag (i.e. not overwriting nor updating).
+ (merge 3ae851e ph/tag-force-no-warn-on-creation later to maint).
+
+ * "git p4" did not behave well when the path to the root of the P4
+ client was not its real path.
+ (merge bbd8486 pw/p4-symlinked-root later to maint).
+
+ * "git archive" reports a failure when asked to create an archive out
+ of an empty tree. It would be more intuitive to give an empty
+ archive back in such a case.
+ (merge bd54cf1 jk/empty-archive later to maint).
+
+ * When "format-patch" quoted a non-ascii strings on the header files,
+ it incorrectly applied rfc2047 and chopped a single character in
+ the middle of it.
+ (merge 6cd3c05 ks/rfc2047-one-char-at-a-time later to maint).
+
+ * An aliased command spawned from a bare repository that does not say
+ it is bare with "core.bare = yes" is treated as non-bare by mistake.
+ (merge 2cd83d1 jk/alias-in-bare later to maint).
+
+ * In "git reflog expire", REACHABLE bit was not cleared from the
+ correct objects.
+
+ * The logic used by "git diff -M --stat" to shorten the names of
+ files before and after a rename did not work correctly when the
+ common prefix and suffix between the two filenames overlapped.
+
+ * The "--match=<pattern>" option of "git describe", when used with
+ "--all" to allow refs that are not annotated tags to be used as a
+ base of description, did not restrict the output from the command
+ to those that match the given pattern.
+ (merge 46e1d6e jc/describe later to maint).
+
* Clarify in the documentation "what" gets pushed to "where" when the
command line to "git push" does not say these explicitly.
- (merge cfe1348 jc/maint-push-refspec-default-doc later to maint).
* The "--color=<when>" argument to the commands in the diff family
was described poorly.
- (merge 3d0e75f jc/color-diff-doc later to maint).
* The arguments given to pre-rebase hook were not documented.
- (merge 0414acc wk/doc-pre-rebase later to maint).
* The v4 index format was not documented.
- (merge 647d879 nd/doc-index-format later to maint).
* The "--match=<pattern>" argument "git describe" takes uses glob
pattern but it wasn't obvious from the documentation.
- (merge 5229149 gp/describe-match-uses-glob-pattern later to maint).
* Some sources failed to compile on systems that lack NI_MAXHOST in
their system header (e.g. z/OS).
- (merge 3b130ade dm/ni-maxhost-may-be-missing later to maint).
* Add an example use of "--env-filter" in "filter-branch"
documentation.
- (merge 21b6e4f tk/doc-filter-branch later to maint).
* "git bundle verify" did not say "records a complete history" for a
bundle that does not have any prerequisites.
- (merge a02ffe0 lf/bundle-verify-list-prereqs later to maint).
* In the v1.8.0 era, we changed symbols that do not have to be global
to file scope static, but a few functions in graph.c were used by
CGit from sideways bypassing the entry points of the API the
in-tree users use.
- (merge ac751a0 jk/graph-c-expose-symbols-for-cgit later to maint).
* "git update-index -h" did not do the usual "-h(elp)" thing.
* "git index-pack" had a buffer-overflow while preparing an
informational message when the translated version of it was too
long.
- (merge 5c3459f nd/index-pack-l10n-buf-overflow later to maint).
* 'git commit -m "$msg"' used to add an extra newline even when
$msg already ended with one.
* The SSL peer verification done by "git imap-send" did not ask for
Server Name Indication (RFC 4366), failing to connect SSL/TLS
sites that serve multiple hostnames on a single IP.
- (merge 698a1ec ob/imap-send-ssl-verify later to maint).
* perl/Git.pm::cat_blob slurped everything in core only to write it
out to a file descriptor, which was not a very smart thing to do.
- (merge 712c6ad jc/perl-cat-blob later to maint).
* "git branch" did not bother to check nonsense command line
parameters and issue errors in many cases.
- (merge 8efb889 nd/branch-error-cases later to maint).
* Verification of signed tags were not done correctly when not in C
or en/US locale.
most official "UTF-8" when the system does not understand the
user-supplied encoding name that are the common alternative
spellings of UTF-8.
- (merge 5c680be jk/utf-8-can-be-spelled-differently later to maint).
* When export-subst is used, "zip" output recorded incorrect
size of the file.
- (merge d3c1472 rs/zip-compresssed-size-with-export-subst later to maint).
* "git am $maildir/" applied messages in an unexpected order; sort
filenames read from the maildir/ in a way that is more likely to
sort messages in the order the writing MUA meant to, by sorting
numeric segment in numeric order and non-numeric segment in
alphabetical order.
- (merge 18505c3 jk/mailsplit-maildir-muttsort later to maint).
* "git submodule update", when recursed into sub-submodules, did not
acccumulate the prefix paths.
- (merge 75bf5e6 we/submodule-update-prefix-output later to maint).
(default '#').
sequence.editor::
- Text editor used by `git rebase -i` for editing the rebase insn file.
+ Text editor used by `git rebase -i` for editing the rebase instruction file.
The value is meant to be interpreted by the shell when it is used.
It can be overridden by the `GIT_SEQUENCE_EDITOR` environment variable.
When not configured the default commit message editor is used instead.
--------
[verse]
'git commit-tree' <tree> [(-p <parent>)...] < changelog
-'git commit-tree' [(-p <parent>)...] [(-m <message>)...] [(-F <file>)...] <tree>
+'git commit-tree' [(-p <parent>)...] [-S[<keyid>]] [(-m <message>)...]
+ [(-F <file>)...] <tree>
+
DESCRIPTION
-----------
Read the commit log message from the given file. Use `-` to read
from the standard input.
+-S[<keyid>]::
+ GPG-sign commit.
+
Commit Information
------------------
-m <msg>::
--message=<msg>::
Use the given <msg> as the commit message.
+ If multiple `-m` options are given, their values are
+ concatenated as separate paragraphs.
-t <file>::
--template=<file>::
--symlinks::
--no-symlinks::
'git difftool''s default behavior is create symlinks to the
- working tree when run in `--dir-diff` mode.
+ working tree when run in `--dir-diff` mode and the right-hand
+ side of the comparison yields the same content as the file in
+ the working tree.
+
- Specifying `--no-symlinks` instructs 'git difftool' to create
- copies instead. `--no-symlinks` is the default on Windows.
+Specifying `--no-symlinks` instructs 'git difftool' to create copies
+instead. `--no-symlinks` is the default on Windows.
-x <command>::
--extcmd=<command>::
If you tried a merge which resulted in complex conflicts and
want to start over, you can recover with `git merge --abort`.
+MERGING TAG
+-----------
+
+When merging an annotated (and possibly signed) tag, Git always
+creates a merge commit even if a fast-forward merge is possible, and
+the commit message template is prepared with the tag message.
+Additionally, if the tag is signed, the signature check is reported
+as a comment in the message template. See also linkgit:git-tag[1].
+
+When you want to just integrate with the work leading to the commit
+that happens to be tagged, e.g. synchronizing with an upstream
+release point, you may not want to make an unnecessary merge commit.
+
+In such a case, you can "unwrap" the tag yourself before feeding it
+to `git merge`, or pass `--ff-only` when you do not have any work on
+your own. e.g.
+
+---
+git fetch origin
+git merge v1.2.3^0
+git merge --ff-only v1.2.3
+---
+
+
HOW CONFLICTS ARE PRESENTED
---------------------------
------------------------------------------------
-If you tried a pull which resulted in a complex conflicts and
+If you tried a pull which resulted in complex conflicts and
would want to start over, you can recover with 'git reset'.
SYNOPSIS
--------
[verse]
-'git push' [--all | --mirror | --tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
+'git push' [--all | --mirror | --tags] [--follow-tags] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [--prune] [-v | --verbose] [-u | --set-upstream]
[<repository> [<refspec>...]]
addition to refspecs explicitly listed on the command
line.
+--follow-tags::
+ Push all the refs that would be pushed without this option,
+ and also push annotated tags in `refs/tags` that are missing
+ from the remote but are pointing at committish that are
+ reachable from the refs being pushed.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
Ignored files are deemed expendable and won't stop a submodule's work
tree from being removed.
+If you only want to remove the local checkout of a submodule from your
+work tree without committing the removal,
+use linkgit:git-submodule[1] `deinit` instead.
+
EXAMPLES
--------
`git rm Documentation/\*.txt`::
outputs code for use with eval to set the GIT_AUTHOR_NAME,
GIT_AUTHOR_EMAIL and GIT_AUTHOR_DATE variables for a given commit.
+create_virtual_base::
+ modifies the first file so only lines in common with the
+ second file remain. If there is insufficient common material,
+ then the first file is left empty. The result is suitable
+ as a virtual base input for a 3-way merge.
+
GIT
---
Part of the linkgit:git[1] suite
[--reference <repository>] [--] <repository> [<path>]
'git submodule' [--quiet] status [--cached] [--recursive] [--] [<path>...]
'git submodule' [--quiet] init [--] [<path>...]
+'git submodule' [--quiet] deinit [-f|--force] [--] <path>...
'git submodule' [--quiet] update [--init] [--remote] [-N|--no-fetch]
[-f|--force] [--rebase] [--reference <repository>]
[--merge] [--recursive] [--] [<path>...]
the explicit 'init' step if you do not intend to customize
any submodule locations.
+deinit::
+ Unregister the given submodules, i.e. remove the whole
+ `submodule.$name` section from .git/config together with their work
+ tree. Further calls to `git submodule update`, `git submodule foreach`
+ and `git submodule sync` will skip any unregistered submodules until
+ they are initialized again, so use this command if you don't want to
+ have a local checkout of the submodule in your work tree anymore. If
+ you really want to remove a submodule from the repository and commit
+ that use linkgit:git-rm[1] instead.
++
+If `--force` is specified, the submodule's work tree will be removed even if
+it contains local modifications.
+
update::
Update the registered submodules, i.e. clone missing submodules and
checkout the commit specified in the index of the containing repository.
-f::
--force::
- This option is only valid for add and update commands.
+ This option is only valid for add, deinit and update commands.
When running add, allow adding an otherwise ignored submodule path.
+ When running deinit the submodule work trees will be removed even if
+ they contain local changes.
When running update, throw away local changes in submodules when
switching to a different commit; and always run a checkout operation
in the submodule, even if the commit listed in the index of the
If this environment variable is set then 'git fetch'
and 'git push' will use this command instead
of 'ssh' when they need to connect to a remote system.
- The '$GIT_SSH' command will be given exactly two arguments:
- the 'username@host' (or just 'host') from the URL and the
- shell command to execute on that remote system.
+ The '$GIT_SSH' command will be given exactly two or
+ four arguments: the 'username@host' (or just 'host')
+ from the URL and the shell command to execute on that
+ remote system, optionally preceded by '-p' (literally) and
+ the 'port' from the URL when it specifies something other
+ than the default SSH port.
+
To pass options to the program that you want to list in GIT_SSH
you will need to wrap the program and options into a shell script,
--no-ff::
Create a merge commit even when the merge resolves as a
- fast-forward.
+ fast-forward. This is the default behaviour when merging an
+ annotated (and possibly signed) tag.
--ff-only::
Refuse to merge and exit with a non-zero status unless the
# Among the variables below, these:
# gitexecdir
# template_dir
-# mandir
-# infodir
-# htmldir
# sysconfdir
# can be specified as a relative path some/where/else;
# this is interpreted as relative to $(prefix) and "git" at
# runtime figures out where they are based on the path to the executable.
+# Additionally, the following will be treated as relative by "git" if they
+# begin with "$(prefix)/":
+# mandir
+# infodir
+# htmldir
# This can help installing the suite in a relocatable way.
prefix = $(HOME)
bindir_relative = bin
bindir = $(prefix)/$(bindir_relative)
-mandir = share/man
-infodir = share/info
+mandir = $(prefix)/share/man
+infodir = $(prefix)/share/info
gitexecdir = libexec/git-core
mergetoolsdir = $(gitexecdir)/mergetools
sharedir = $(prefix)/share
gitwebdir = $(sharedir)/gitweb
localedir = $(sharedir)/locale
template_dir = share/git-core/templates
-htmldir = share/doc/git-doc
+htmldir = $(prefix)/share/doc/git-doc
ETC_GITCONFIG = $(sysconfdir)/gitconfig
ETC_GITATTRIBUTES = $(sysconfdir)/gitattributes
lib = lib
# DESTDIR =
pathsep = :
+mandir_relative = $(patsubst $(prefix)/%,%,$(mandir))
+infodir_relative = $(patsubst $(prefix)/%,%,$(infodir))
+htmldir_relative = $(patsubst $(prefix)/%,%,$(htmldir))
+
export prefix bindir sharedir sysconfdir gitwebdir localedir
CC = cc
DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
bindir_SQ = $(subst ','\'',$(bindir))
bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
-mandir_SQ = $(subst ','\'',$(mandir))
-infodir_SQ = $(subst ','\'',$(infodir))
+mandir_relative_SQ = $(subst ','\'',$(mandir_relative))
+infodir_relative_SQ = $(subst ','\'',$(infodir_relative))
localedir_SQ = $(subst ','\'',$(localedir))
gitexecdir_SQ = $(subst ','\'',$(gitexecdir))
template_dir_SQ = $(subst ','\'',$(template_dir))
-htmldir_SQ = $(subst ','\'',$(htmldir))
+htmldir_relative_SQ = $(subst ','\'',$(htmldir_relative))
prefix_SQ = $(subst ','\'',$(prefix))
gitwebdir_SQ = $(subst ','\'',$(gitwebdir))
git.sp git.s git.o: GIT-PREFIX
git.sp git.s git.o: EXTRA_CPPFLAGS = \
- '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
- '-DGIT_MAN_PATH="$(mandir_SQ)"' \
- '-DGIT_INFO_PATH="$(infodir_SQ)"'
+ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
git$X: git.o GIT-LDFLAGS $(BUILTIN_OBJS) $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ git.o \
builtin/help.sp builtin/help.s builtin/help.o: common-cmds.h GIT-PREFIX
builtin/help.sp builtin/help.s builtin/help.o: EXTRA_CPPFLAGS = \
- '-DGIT_HTML_PATH="$(htmldir_SQ)"' \
- '-DGIT_MAN_PATH="$(mandir_SQ)"' \
- '-DGIT_INFO_PATH="$(infodir_SQ)"'
+ '-DGIT_HTML_PATH="$(htmldir_relative_SQ)"' \
+ '-DGIT_MAN_PATH="$(mandir_relative_SQ)"' \
+ '-DGIT_INFO_PATH="$(infodir_relative_SQ)"'
version.sp version.s version.o: GIT-VERSION-FILE GIT-USER-AGENT
version.sp version.s version.o: EXTRA_CPPFLAGS = \
dest[3] = 0xff & (n >> 030);
}
-static void *zlib_deflate(void *data, unsigned long size,
- int compression_level, unsigned long *compressed_size)
+static void *zlib_deflate_raw(void *data, unsigned long size,
+ int compression_level,
+ unsigned long *compressed_size)
{
git_zstream stream;
unsigned long maxsize;
int result;
memset(&stream, 0, sizeof(stream));
- git_deflate_init(&stream, compression_level);
+ git_deflate_init_raw(&stream, compression_level);
maxsize = git_deflate_bound(&stream, size);
buffer = xmalloc(maxsize);
}
if (buffer && method == 8) {
- deflated = zlib_deflate(buffer, size, args->compression_level,
- &compressed_size);
- if (deflated && compressed_size - 6 < size) {
- /* ZLIB --> raw compressed data (see RFC 1950) */
- /* CMF and FLG ... */
- out = (unsigned char *)deflated + 2;
- compressed_size -= 6; /* ... and ADLER32 */
- } else {
+ out = deflated = zlib_deflate_raw(buffer, size,
+ args->compression_level,
+ &compressed_size);
+ if (!out || compressed_size >= size) {
+ out = buffer;
method = 0;
compressed_size = size;
}
unsigned char compressed[STREAM_BUFFER_SIZE * 2];
memset(&zstream, 0, sizeof(zstream));
- git_deflate_init(&zstream, args->compression_level);
+ git_deflate_init_raw(&zstream, args->compression_level);
compressed_size = 0;
zstream.next_out = compressed;
result = git_deflate(&zstream, 0);
if (result != Z_OK)
die("deflate error (%d)", result);
- out = compressed;
- if (!compressed_size)
- out += 2;
- out_len = zstream.next_out - out;
+ out_len = zstream.next_out - compressed;
if (out_len > 0) {
- write_or_die(1, out, out_len);
+ write_or_die(1, compressed, out_len);
compressed_size += out_len;
zstream.next_out = compressed;
zstream.avail_out = sizeof(compressed);
die("deflate error (%d)", result);
git_deflate_end(&zstream);
- out = compressed;
- if (!compressed_size)
- out += 2;
- out_len = zstream.next_out - out - 4;
- write_or_die(1, out, out_len);
+ out_len = zstream.next_out - compressed;
+ write_or_die(1, compressed, out_len);
compressed_size += out_len;
zip_offset += compressed_size;
ar_args->pathspec = pathspec = get_pathspec("", pathspec);
if (pathspec) {
while (*pathspec) {
- if (!path_exists(ar_args->tree, *pathspec))
+ if (**pathspec && !path_exists(ar_args->tree, *pathspec))
die("path not found: %s", *pathspec);
pathspec++;
}
const char *remote, const char *exec,
const char *name_hint)
{
- char buf[LARGE_PACKET_MAX];
- int fd[2], i, len, rv;
+ char *buf;
+ int fd[2], i, rv;
struct transport *transport;
struct remote *_remote;
packet_write(fd[1], "argument %s\n", argv[i]);
packet_flush(fd[1]);
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (!len)
+ buf = packet_read_line(fd[0], NULL);
+ if (!buf)
die(_("git archive: expected ACK/NAK, got EOF"));
- if (buf[len-1] == '\n')
- buf[--len] = 0;
if (strcmp(buf, "ACK")) {
- if (len > 5 && !prefixcmp(buf, "NACK "))
+ if (!prefixcmp(buf, "NACK "))
die(_("git archive: NACK %s"), buf + 5);
- if (len > 4 && !prefixcmp(buf, "ERR "))
+ if (!prefixcmp(buf, "ERR "))
die(_("remote error: %s"), buf + 4);
die(_("git archive: protocol error"));
}
- len = packet_read_line(fd[0], buf, sizeof(buf));
- if (len)
+ if (packet_read_line(fd[0], NULL))
die(_("git archive: expected a flush"));
/* Now, start reading from fd[0] and spit it out to stdout */
#include "string-list.h"
#include "column.h"
#include "utf8.h"
+#include "wt-status.h"
static const char * const builtin_branch_usage[] = {
N_("git branch [options] [-r | -a] [--merged | --no-merged]"),
return w;
}
+static char *get_head_description(void)
+{
+ struct strbuf desc = STRBUF_INIT;
+ struct wt_status_state state;
+ memset(&state, 0, sizeof(state));
+ wt_status_get_state(&state, 1);
+ if (state.rebase_in_progress ||
+ state.rebase_interactive_in_progress)
+ strbuf_addf(&desc, _("(no branch, rebasing %s)"),
+ state.branch);
+ else if (state.bisect_in_progress)
+ strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
+ state.branch);
+ else if (state.detached_from)
+ strbuf_addf(&desc, _("(detached from %s)"),
+ state.detached_from);
+ else
+ strbuf_addstr(&desc, _("(no branch)"));
+ free(state.branch);
+ free(state.onto);
+ free(state.detached_from);
+ return strbuf_detach(&desc, NULL);
+}
static void show_detached(struct ref_list *ref_list)
{
if (head_commit && is_descendant_of(head_commit, ref_list->with_commit)) {
struct ref_item item;
- item.name = xstrdup(_("(no branch)"));
+ item.name = get_head_description();
item.width = utf8_strwidth(item.name);
item.kind = REF_LOCAL_BRANCH;
item.dest = NULL;
unsigned char sha1[20];
enum object_type type = 0;
unsigned long size;
- void *contents;
+ void *contents = NULL;
if (!obj_name)
return 1;
#include "utf8.h"
#include "gpg-interface.h"
-static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S<signer>] [-m <message>] [-F <file>] <sha1> <changelog";
+static const char commit_tree_usage[] = "git commit-tree [(-p <sha1>)...] [-S[<keyid>]] [-m <message>] [-F <file>] <sha1> <changelog";
static void new_parent(struct commit *parent, struct commit_list **parents_p)
{
previous = eol;
}
- append_signoff(&sb, ignore_footer);
+ append_signoff(&sb, ignore_footer, 0);
}
if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len)
static int get_name(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
- int might_be_tag = !prefixcmp(path, "refs/tags/");
+ int is_tag = !prefixcmp(path, "refs/tags/");
unsigned char peeled[20];
- int is_tag, prio;
+ int is_annotated, prio;
- if (!all && !might_be_tag)
+ /* Reject anything outside refs/tags/ unless --all */
+ if (!all && !is_tag)
return 0;
+ /* Accept only tags that match the pattern, if given */
+ if (pattern && (!is_tag || fnmatch(pattern, path + 10, 0)))
+ return 0;
+
+ /* Is it annotated? */
if (!peel_ref(path, peeled)) {
- is_tag = !!hashcmp(sha1, peeled);
+ is_annotated = !!hashcmp(sha1, peeled);
} else {
hashcpy(peeled, sha1);
- is_tag = 0;
+ is_annotated = 0;
}
- /* If --all, then any refs are used.
- * If --tags, then any tags are used.
- * Otherwise only annotated tags are used.
+ /*
+ * By default, we only use annotated tags, but with --tags
+ * we fall back to lightweight ones (even without --tags,
+ * we still remember lightweight ones, only to give hints
+ * in an error message). --all allows any refs to be used.
*/
- if (might_be_tag) {
- if (is_tag)
- prio = 2;
- else
- prio = 1;
-
- if (pattern && fnmatch(pattern, path + 10, 0))
- prio = 0;
- }
+ if (is_annotated)
+ prio = 2;
+ else if (is_tag)
+ prio = 1;
else
prio = 0;
- if (!all) {
- if (!prio)
- return 0;
- }
add_to_known_names(all ? path + 5 : path + 10, peeled, prio, sha1);
return 0;
}
printf("progress %d objects\n", counter);
}
-static void handle_object(const unsigned char *sha1)
+static void export_blob(const unsigned char *sha1)
{
unsigned long size;
enum object_type type;
char *buf;
struct object *object;
+ int eaten;
if (no_data)
return;
if (is_null_sha1(sha1))
return;
- object = parse_object(sha1);
- if (!object)
- die ("Could not read blob %s", sha1_to_hex(sha1));
-
- if (object->flags & SHOWN)
+ object = lookup_object(sha1);
+ if (object && object->flags & SHOWN)
return;
buf = read_sha1_file(sha1, &type, &size);
if (!buf)
die ("Could not read blob %s", sha1_to_hex(sha1));
+ if (check_sha1_signature(sha1, buf, size, typename(type)) < 0)
+ die("sha1 mismatch in blob %s", sha1_to_hex(sha1));
+ object = parse_object_buffer(sha1, type, size, buf, &eaten);
+ if (!object)
+ die("Could not read blob %s", sha1_to_hex(sha1));
mark_next_object(object);
show_progress();
object->flags |= SHOWN;
- free(buf);
+ if (!eaten)
+ free(buf);
}
static int depth_first(const void *a_, const void *b_)
/* Export the referenced blobs, and remember the marks. */
for (i = 0; i < diff_queued_diff.nr; i++)
if (!S_ISGITLINK(diff_queued_diff.queue[i]->two->mode))
- handle_object(diff_queued_diff.queue[i]->two->sha1);
+ export_blob(diff_queued_diff.queue[i]->two->sha1);
mark_next_object(&commit->object);
if (!is_encoding_utf8(encoding))
commit = (struct commit *)tag;
break;
case OBJ_BLOB:
- handle_object(tag->object.sha1);
+ export_blob(tag->object.sha1);
continue;
default: /* OBJ_TAG (nested tags) is already handled */
warning("Tag points to object of unexpected type %s, skipping.",
/* in stateless RPC mode we use pkt-line to read
* from stdin, until we get a flush packet
*/
- static char line[1000];
for (;;) {
- int n = packet_read_line(0, line, sizeof(line));
- if (!n)
+ char *line = packet_read_line(0, NULL);
+ if (!line)
break;
- if (line[n-1] == '\n')
- n--;
- add_sought_entry_mem(&sought, &nr_sought, &alloc_sought, line, n);
+ add_sought_entry(&sought, &nr_sought, &alloc_sought, line);
}
}
else {
args.verbose ? CONNECT_VERBOSE : 0);
}
- get_remote_heads(fd[0], &ref, 0, NULL);
+ get_remote_heads(fd[0], NULL, 0, &ref, 0, NULL);
ref = fetch_pack(&args, fd, conn, ref, dest,
sought, nr_sought, pack_lockfile_ptr);
unsigned char sha1[20];
/* Is it a rev? */
if (!get_sha1(arg, sha1)) {
- struct object *object = parse_object(sha1);
- if (!object)
- die(_("bad object %s"), arg);
+ struct object *object = parse_object_or_die(sha1, arg);
if (!seen_dashdash)
verify_non_filename(prefix, arg);
add_object_array(object, arg, &list);
static int from_stdin;
static int strict;
static int verbose;
+static int show_stat;
static struct progress *progress;
#define work_lock() lock_mutex(&work_mutex)
#define work_unlock() unlock_mutex(&work_mutex)
+static pthread_mutex_t deepest_delta_mutex;
+#define deepest_delta_lock() lock_mutex(&deepest_delta_mutex)
+#define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex)
+
static pthread_key_t key;
static inline void lock_mutex(pthread_mutex_t *mutex)
init_recursive_mutex(&read_mutex);
pthread_mutex_init(&counter_mutex, NULL);
pthread_mutex_init(&work_mutex, NULL);
+ if (show_stat)
+ pthread_mutex_init(&deepest_delta_mutex, NULL);
pthread_key_create(&key, NULL);
thread_data = xcalloc(nr_threads, sizeof(*thread_data));
threads_active = 1;
pthread_mutex_destroy(&read_mutex);
pthread_mutex_destroy(&counter_mutex);
pthread_mutex_destroy(&work_mutex);
+ if (show_stat)
+ pthread_mutex_destroy(&deepest_delta_mutex);
pthread_key_delete(key);
free(thread_data);
}
#define work_lock()
#define work_unlock()
+#define deepest_delta_lock()
+#define deepest_delta_unlock()
+
#endif
void *base_data, *delta_data;
delta_obj->real_type = base->obj->real_type;
- delta_obj->delta_depth = base->obj->delta_depth + 1;
- if (deepest_delta < delta_obj->delta_depth)
- deepest_delta = delta_obj->delta_depth;
+ if (show_stat) {
+ delta_obj->delta_depth = base->obj->delta_depth + 1;
+ deepest_delta_lock();
+ if (deepest_delta < delta_obj->delta_depth)
+ deepest_delta = delta_obj->delta_depth;
+ deepest_delta_unlock();
+ }
delta_obj->base_object_no = base->obj - objects;
delta_data = get_data_from_pack(delta_obj);
base_data = get_base_data(base);
set_thread_data(data);
for (;;) {
int i;
- work_lock();
+ counter_lock();
display_progress(progress, nr_resolved_deltas);
+ counter_unlock();
+ work_lock();
while (nr_dispatched < nr_objects &&
is_delta_type(objects[nr_dispatched].type))
nr_dispatched++;
objects = xrealloc(objects,
(nr_objects + nr_unresolved + 1)
* sizeof(*objects));
+ memset(objects + nr_objects + 1, 0,
+ nr_unresolved * sizeof(*objects));
f = sha1fd(output_fd, curr_pack);
fix_unresolved_deltas(f, nr_unresolved);
strbuf_addf(&msg, _("completed with %d local objects"),
int cmd_index_pack(int argc, const char **argv, const char *prefix)
{
- int i, fix_thin_pack = 0, verify = 0, stat_only = 0, stat = 0;
+ int i, fix_thin_pack = 0, verify = 0, stat_only = 0;
const char *curr_pack, *curr_index;
const char *index_name = NULL, *pack_name = NULL;
const char *keep_name = NULL, *keep_msg = NULL;
verify = 1;
} else if (!strcmp(arg, "--verify-stat")) {
verify = 1;
- stat = 1;
+ show_stat = 1;
} else if (!strcmp(arg, "--verify-stat-only")) {
verify = 1;
- stat = 1;
+ show_stat = 1;
stat_only = 1;
} else if (!strcmp(arg, "--keep")) {
keep_msg = "";
if (strict)
check_objects();
- if (stat)
+ if (show_stat)
show_pack_info(stat_only);
idx_objects = xmalloc((nr_objects) * sizeof(struct pack_idx_entry *));
#include "streaming.h"
#include "version.h"
#include "mailmap.h"
+#include "gpg-interface.h"
/* Set a default date-time format for git log ("log.date" config variable) */
static const char *default_date_mode = NULL;
if (grep_config(var, value, cb) < 0)
return -1;
+ if (git_gpg_config(var, value, cb) < 0)
+ return -1;
return git_diff_ui_config(var, value, cb);
}
struct commit *origin = NULL, *head = NULL;
const char *in_reply_to = NULL;
struct patch_ids ids;
- char *add_signoff = NULL;
struct strbuf buf = STRBUF_INIT;
int use_patch_format = 0;
int quiet = 0;
rev.subject_prefix = strbuf_detach(&sprefix, NULL);
}
- if (do_signoff) {
- const char *committer;
- const char *endpos;
- committer = git_committer_info(IDENT_STRICT);
- endpos = strchr(committer, '>');
- if (!endpos)
- die(_("bogus committer info %s"), committer);
- add_signoff = xmemdupz(committer, endpos - committer + 1);
- }
-
for (i = 0; i < extra_hdr.nr; i++) {
strbuf_addstr(&buf, extra_hdr.items[i].string);
strbuf_addch(&buf, '\n');
total++;
start_number--;
}
- rev.add_signoff = add_signoff;
+ rev.add_signoff = do_signoff;
while (0 <= --nr) {
int shown;
commit = list[nr];
unsigned dirmask = 0, mask = 0;
for (i = 0; i < 3; i++) {
- mask |= (1 << 1);
+ mask |= (1 << i);
if (n[i].mode && S_ISDIR(n[i].mode))
dirmask |= (1 << i);
}
const char *name = *argv++;
if (!get_sha1(name, sha1)) {
- struct object *object = parse_object(sha1);
- if (!object)
- die("bad object: %s", name);
+ struct object *object = parse_object_or_die(sha1, name);
add_pending_object(&revs, object, "");
}
else
OPT_BIT(0, "prune", &flags, N_("prune locally removed refs"),
TRANSPORT_PUSH_PRUNE),
OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK),
+ OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"),
+ TRANSPORT_PUSH_FOLLOW_TAGS),
OPT_END()
};
struct command *commands = NULL;
struct command **p = &commands;
for (;;) {
- static char line[1000];
+ char *line;
unsigned char old_sha1[20], new_sha1[20];
struct command *cmd;
char *refname;
int len, reflen;
- len = packet_read_line(0, line, sizeof(line));
- if (!len)
+ line = packet_read_line(0, &len);
+ if (!line)
break;
- if (line[len-1] == '\n')
- line[--len] = 0;
if (len < 83 ||
line[40] != ' ' ||
line[81] != ' ' ||
if (use_sideband)
send_sideband(1, 1, buf.buf, buf.len, use_sideband);
else
- safe_write(1, buf.buf, buf.len);
+ write_or_die(1, buf.buf, buf.len);
strbuf_release(&buf);
}
if (cb.unreachable_expire_kind == UE_HEAD) {
struct commit_list *elem;
for (elem = tips; elem; elem = elem->next)
- clear_commit_marks(tip_commit, REACHABLE);
+ clear_commit_marks(elem->item, REACHABLE);
free_commit_list(tips);
} else {
clear_commit_marks(tip_commit, REACHABLE);
}
strbuf_addch(&buf, '\n');
- safe_write(1, buf.buf, buf.len);
+ write_or_die(1, buf.buf, buf.len);
}
strbuf_release(&buf);
}
memset(&extra_have, 0, sizeof(extra_have));
- get_remote_heads(fd[0], &remote_refs, REF_NORMAL, &extra_have);
+ get_remote_heads(fd[0], NULL, 0, &remote_refs, REF_NORMAL, &extra_have);
transport_verify_remote_names(nr_refspecs, refspecs);
die(_("%s: cannot lock the ref"), ref.buf);
if (write_ref_sha1(lock, object, NULL) < 0)
die(_("%s: cannot update the ref"), ref.buf);
- if (force && hashcmp(prev, object))
+ if (force && !is_null_sha1(prev) && hashcmp(prev, object))
printf(_("Updated tag '%s' (was %s)\n"), tag, find_unique_abbrev(prev, DEFAULT_ABBREV));
strbuf_release(&buf);
#include "pkt-line.h"
#include "sideband.h"
#include "run-command.h"
+#include "argv-array.h"
static const char upload_archive_usage[] =
"git upload-archive <repo>";
int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix)
{
- const char *sent_argv[MAX_ARGS];
+ struct argv_array sent_argv = ARGV_ARRAY_INIT;
const char *arg_cmd = "argument ";
- char *p, buf[4096];
- int sent_argc;
- int len;
if (argc != 2)
usage(upload_archive_usage);
- if (strlen(argv[1]) + 1 > sizeof(buf))
- die("insanely long repository name");
-
- strcpy(buf, argv[1]); /* enter-repo smudges its argument */
-
- if (!enter_repo(buf, 0))
- die("'%s' does not appear to be a git repository", buf);
+ if (!enter_repo(argv[1], 0))
+ die("'%s' does not appear to be a git repository", argv[1]);
/* put received options in sent_argv[] */
- sent_argc = 1;
- sent_argv[0] = "git-upload-archive";
- for (p = buf;;) {
- /* This will die if not enough free space in buf */
- len = packet_read_line(0, p, (buf + sizeof buf) - p);
- if (len == 0)
+ argv_array_push(&sent_argv, "git-upload-archive");
+ for (;;) {
+ char *buf = packet_read_line(0, NULL);
+ if (!buf)
break; /* got a flush */
- if (sent_argc > MAX_ARGS - 2)
- die("Too many options (>%d)", MAX_ARGS - 2);
+ if (sent_argv.argc > MAX_ARGS)
+ die("Too many options (>%d)", MAX_ARGS - 1);
- if (p[len-1] == '\n') {
- p[--len] = 0;
- }
- if (len < strlen(arg_cmd) ||
- strncmp(arg_cmd, p, strlen(arg_cmd)))
+ if (prefixcmp(buf, arg_cmd))
die("'argument' token or flush expected");
-
- len -= strlen(arg_cmd);
- memmove(p, p + strlen(arg_cmd), len);
- sent_argv[sent_argc++] = p;
- p += len;
- *p++ = 0;
+ argv_array_push(&sent_argv, buf + strlen(arg_cmd));
}
- sent_argv[sent_argc] = NULL;
/* parse all options sent by the client */
- return write_archive(sent_argc, sent_argv, prefix, 0, NULL, 1);
+ return write_archive(sent_argv.argc, sent_argv.argv, prefix, 0, NULL, 1);
}
__attribute__((format (printf, 1, 2)))
if (buf.len > 0 && buf.buf[0] == '-') {
write_or_die(bundle_fd, buf.buf, buf.len);
if (!get_sha1_hex(buf.buf + 1, sha1)) {
- struct object *object = parse_object(sha1);
+ struct object *object = parse_object_or_die(sha1, buf.buf);
object->flags |= UNINTERESTING;
add_pending_object(&revs, object, xstrdup(buf.buf));
}
} else if (!get_sha1_hex(buf.buf, sha1)) {
- struct object *object = parse_object(sha1);
+ struct object *object = parse_object_or_die(sha1, buf.buf);
object->flags |= SHOWN;
}
}
* end up triggering "empty bundle"
* error.
*/
- obj = parse_object(sha1);
+ obj = parse_object_or_die(sha1, e->name);
obj->flags |= SHOWN;
add_pending_object(&revs, obj, e->name);
}
void git_deflate_init(git_zstream *, int level);
void git_deflate_init_gzip(git_zstream *, int level);
+void git_deflate_init_raw(git_zstream *, int level);
void git_deflate_end(git_zstream *);
int git_deflate_abort(git_zstream *);
int git_deflate_end_gently(git_zstream *);
unsigned int ce_namelen;
unsigned char sha1[20];
struct cache_entry *next;
- struct cache_entry *dir_next;
char name[FLEX_ARRAY]; /* more */
};
unsigned name_hash_initialized : 1,
initialized : 1;
struct hash_table name_hash;
+ struct hash_table dir_hash;
};
extern struct index_state the_index;
/* Name hashing */
extern void add_name_hash(struct index_state *istate, struct cache_entry *ce);
-/*
- * We don't actually *remove* it, we can just mark it invalid so that
- * we won't find it in lookups.
- *
- * Not only would we have to search the lists (simple enough), but
- * we'd also have to rehash other hash buckets in case this makes the
- * hash bucket empty (common). So it's much better to just mark
- * it.
- */
-static inline void remove_name_hash(struct cache_entry *ce)
-{
- ce->ce_flags |= CE_UNHASHED;
-}
+extern void remove_name_hash(struct index_state *istate, struct cache_entry *ce);
+extern void free_name_hash(struct index_state *istate);
#ifndef NO_THE_INDEX_COMPATIBILITY_MACROS
OBJ_BLOB;
}
+/* Double-check local_repo_env below if you add to this list. */
#define GIT_DIR_ENVIRONMENT "GIT_DIR"
#define GIT_NAMESPACE_ENVIRONMENT "GIT_NAMESPACE"
#define GIT_WORK_TREE_ENVIRONMENT "GIT_WORK_TREE"
+#define GIT_PREFIX_ENVIRONMENT "GIT_PREFIX"
#define DEFAULT_GIT_DIR_ENVIRONMENT ".git"
#define DB_ENVIRONMENT "GIT_OBJECT_DIRECTORY"
#define INDEX_ENVIRONMENT "GIT_INDEX_FILE"
#define GIT_LITERAL_PATHSPECS_ENVIRONMENT "GIT_LITERAL_PATHSPECS"
/*
- * Repository-local GIT_* environment variables
- * The array is NULL-terminated to simplify its usage in contexts such
- * environment creation or simple walk of the list.
- * The number of non-NULL entries is available as a macro.
+ * This environment variable is expected to contain a boolean indicating
+ * whether we should or should not treat:
+ *
+ * GIT_DIR=foo.git git ...
+ *
+ * as if GIT_WORK_TREE=. was given. It's not expected that users will make use
+ * of this, but we use it internally to communicate to sub-processes that we
+ * are in a bare repo. If not set, defaults to true.
+ */
+#define GIT_IMPLICIT_WORK_TREE_ENVIRONMENT "GIT_IMPLICIT_WORK_TREE"
+
+/*
+ * Repository-local GIT_* environment variables; these will be cleared
+ * when git spawns a sub-process that runs inside another repository.
+ * The array is NULL-terminated, which makes it easy to pass in the "env"
+ * parameter of a run-command invocation, or to do a simple walk.
*/
-#define LOCAL_REPO_ENV_SIZE 9
-extern const char *const local_repo_env[LOCAL_REPO_ENV_SIZE + 1];
+extern const char * const local_repo_env[];
extern int is_bare_repository_cfg;
extern int is_bare_repository(void);
int nr, alloc;
unsigned char (*array)[20];
};
-extern struct ref **get_remote_heads(int in, struct ref **list, unsigned int flags, struct extra_have_objects *);
+extern struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+ struct ref **list, unsigned int flags,
+ struct extra_have_objects *);
extern int server_supports(const char *feature);
extern int parse_feature_request(const char *features, const char *feature);
extern const char *server_feature_value(const char *feature, int *len_ret);
#include "diffcore.h"
#include "quote.h"
#include "xdiff-interface.h"
+#include "xdiff/xmacros.h"
#include "log-tree.h"
#include "refs.h"
#include "userdiff.h"
return blob;
}
-static void append_lost(struct sline *sline, int n, const char *line, int len)
+static int match_string_spaces(const char *line1, int len1,
+ const char *line2, int len2,
+ long flags)
+{
+ if (flags & XDF_WHITESPACE_FLAGS) {
+ for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--);
+ for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--);
+ }
+
+ if (!(flags & (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE)))
+ return (len1 == len2 && !memcmp(line1, line2, len1));
+
+ while (len1 > 0 && len2 > 0) {
+ len1--;
+ len2--;
+ if (XDL_ISSPACE(line1[len1]) || XDL_ISSPACE(line2[len2])) {
+ if ((flags & XDF_IGNORE_WHITESPACE_CHANGE) &&
+ (!XDL_ISSPACE(line1[len1]) || !XDL_ISSPACE(line2[len2])))
+ return 0;
+
+ for (; len1 > 0 && XDL_ISSPACE(line1[len1]); len1--);
+ for (; len2 > 0 && XDL_ISSPACE(line2[len2]); len2--);
+ }
+ if (line1[len1] != line2[len2])
+ return 0;
+ }
+
+ if (flags & XDF_IGNORE_WHITESPACE) {
+ /* Consume remaining spaces */
+ for (; len1 > 0 && XDL_ISSPACE(line1[len1 - 1]); len1--);
+ for (; len2 > 0 && XDL_ISSPACE(line2[len2 - 1]); len2--);
+ }
+
+ /* We matched full line1 and line2 */
+ if (!len1 && !len2)
+ return 1;
+
+ return 0;
+}
+
+static void append_lost(struct sline *sline, int n, const char *line, int len, long flags)
{
struct lline *lline;
unsigned long this_mask = (1UL<<n);
if (sline->lost_head) {
lline = sline->next_lost;
while (lline) {
- if (lline->len == len &&
- !memcmp(lline->line, line, len)) {
+ if (match_string_spaces(lline->line, lline->len,
+ line, len, flags)) {
lline->parent_map |= this_mask;
sline->next_lost = lline->next;
return;
int n;
struct sline *sline;
struct sline *lost_bucket;
+ long flags;
};
static void consume_line(void *state_, char *line, unsigned long len)
return; /* not in any hunk yet */
switch (line[0]) {
case '-':
- append_lost(state->lost_bucket, state->n, line+1, len-1);
+ append_lost(state->lost_bucket, state->n, line+1, len-1, state->flags);
break;
case '+':
state->sline[state->lno-1].flag |= state->nmask;
struct sline *sline, unsigned int cnt, int n,
int num_parent, int result_deleted,
struct userdiff_driver *textconv,
- const char *path)
+ const char *path, long flags)
{
unsigned int p_lno, lno;
unsigned long nmask = (1UL << n);
parent_file.ptr = grab_blob(parent, mode, &sz, textconv, path);
parent_file.size = sz;
memset(&xpp, 0, sizeof(xpp));
- xpp.flags = 0;
+ xpp.flags = flags;
memset(&xecfg, 0, sizeof(xecfg));
memset(&state, 0, sizeof(state));
+ state.flags = flags;
state.nmask = nmask;
state.sline = sline;
state.lno = 1;
elem->parent[i].mode,
&result_file, sline,
cnt, i, num_parent, result_deleted,
- textconv, elem->path);
+ textconv, elem->path, opt->xdl_opts);
}
show_hunks = make_hunks(sline, cnt, num_parent, dense);
}
}
-void clear_commit_marks(struct commit *commit, unsigned int mark)
+void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark)
{
struct commit_list *list = NULL;
- commit_list_insert(commit, &list);
+
+ while (nr--) {
+ commit_list_insert(*commit, &list);
+ commit++;
+ }
while (list)
clear_commit_marks_1(&list, pop_commit(&list), mark);
}
+void clear_commit_marks(struct commit *commit, unsigned int mark)
+{
+ clear_commit_marks_many(1, &commit, mark);
+}
+
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark)
{
struct object *object;
if (!result || !result->next) {
if (cleanup) {
clear_commit_marks(one, all_flags);
- for (i = 0; i < n; i++)
- clear_commit_marks(twos[i], all_flags);
+ clear_commit_marks_many(n, twos, all_flags);
}
return result;
}
free_commit_list(result);
clear_commit_marks(one, all_flags);
- for (i = 0; i < n; i++)
- clear_commit_marks(twos[i], all_flags);
+ clear_commit_marks_many(n, twos, all_flags);
cnt = remove_redundant(rslt, cnt);
result = NULL;
}
/*
- * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ * Is "commit" an ancestor of one of the "references"?
*/
-int in_merge_bases(struct commit *commit, struct commit *reference)
+int in_merge_bases_many(struct commit *commit, int nr_reference, struct commit **reference)
{
struct commit_list *bases;
- int ret = 0;
+ int ret = 0, i;
- if (parse_commit(commit) || parse_commit(reference))
+ if (parse_commit(commit))
return ret;
+ for (i = 0; i < nr_reference; i++)
+ if (parse_commit(reference[i]))
+ return ret;
- bases = paint_down_to_common(commit, 1, &reference);
+ bases = paint_down_to_common(commit, nr_reference, reference);
if (commit->object.flags & PARENT2)
ret = 1;
clear_commit_marks(commit, all_flags);
- clear_commit_marks(reference, all_flags);
+ clear_commit_marks_many(nr_reference, reference, all_flags);
free_commit_list(bases);
return ret;
}
+/*
+ * Is "commit" an ancestor of (i.e. reachable from) the "reference"?
+ */
+int in_merge_bases(struct commit *commit, struct commit *reference)
+{
+ return in_merge_bases_many(commit, 1, &reference);
+}
+
struct commit_list *reduce_heads(struct commit_list *heads)
{
struct commit_list *p;
struct commit *pop_commit(struct commit_list **stack);
void clear_commit_marks(struct commit *commit, unsigned int mark);
+void clear_commit_marks_many(int nr, struct commit **commit, unsigned int mark);
void clear_commit_marks_for_object_array(struct object_array *a, unsigned mark);
/*
int is_descendant_of(struct commit *, struct commit_list *);
int in_merge_bases(struct commit *, struct commit *);
+int in_merge_bases_many(struct commit *, int, struct commit **);
extern int interactive_add(int argc, const char **argv, const char *prefix, int patch);
extern int run_add_interactive(const char *revision, const char *patch_mode,
+#define CYGWIN_C
#define WIN32_LEAN_AND_MEAN
#ifdef CYGWIN_V15_WIN32API
#include "../git-compat-util.h"
#endif
#include "../cache.h" /* to read configuration */
+/*
+ * Return POSIX permission bits, regardless of core.ignorecygwinfstricks
+ */
+int cygwin_get_st_mode_bits(const char *path, int *mode)
+{
+ struct stat st;
+ if (lstat(path, &st) < 0)
+ return -1;
+ *mode = st.st_mode;
+ return 0;
+}
+
static inline void filetime_to_timespec(const FILETIME *ft, struct timespec *ts)
{
long long winTime = ((long long)ft->dwHighDateTime << 32) +
typedef int (*stat_fn_t)(const char*, struct stat*);
extern stat_fn_t cygwin_stat_fn;
extern stat_fn_t cygwin_lstat_fn;
+int cygwin_get_st_mode_bits(const char *path, int *mode);
+#define get_st_mode_bits(p,m) cygwin_get_st_mode_bits((p),(m))
+#ifndef CYGWIN_C
+/* cygwin.c needs the original lstat() */
#define stat(path, buf) (*cygwin_stat_fn)(path, buf)
#define lstat(path, buf) (*cygwin_lstat_fn)(path, buf)
+#endif
mandir = @mandir@
htmldir = @htmldir@
-
-srcdir = @srcdir@
-VPATH = @srcdir@
-
-export exec_prefix mandir
-export srcdir VPATH
/*
* Read all the refs from the other end
*/
-struct ref **get_remote_heads(int in, struct ref **list,
- unsigned int flags,
+struct ref **get_remote_heads(int in, char *src_buf, size_t src_len,
+ struct ref **list, unsigned int flags,
struct extra_have_objects *extra_have)
{
int got_at_least_one_head = 0;
for (;;) {
struct ref *ref;
unsigned char old_sha1[20];
- static char buffer[1000];
char *name;
int len, name_len;
+ char *buffer = packet_buffer;
- len = packet_read(in, buffer, sizeof(buffer));
+ len = packet_read(in, &src_buf, &src_len,
+ packet_buffer, sizeof(packet_buffer),
+ PACKET_READ_GENTLE_ON_EOF |
+ PACKET_READ_CHOMP_NEWLINE);
if (len < 0)
die_initial_contact(got_at_least_one_head);
if (!len)
break;
- if (buffer[len-1] == '\n')
- buffer[--len] = 0;
if (len > 4 && !prefixcmp(buffer, "ERR "))
die("remote error: %s", buffer + 4);
{
__git_has_doubledash && return
- local subcommands="add status init update summary foreach sync"
+ local subcommands="add status init deinit update summary foreach sync"
if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
case "$cur" in
--*)
static int execute(void)
{
- static char line[1000];
+ char *line = packet_buffer;
int pktlen, len, i;
char *addr = getenv("REMOTE_ADDR"), *port = getenv("REMOTE_PORT");
loginfo("Connection from %s:%s", addr, port);
alarm(init_timeout ? init_timeout : timeout);
- pktlen = packet_read_line(0, line, sizeof(line));
+ pktlen = packet_read(0, NULL, NULL, packet_buffer, sizeof(packet_buffer), 0);
alarm(0);
len = strlen(line);
* sense to specify timestamp way into the future. Make
* sure it is not later than ten days from now...
*/
- if (now + 10*24*3600 < specified)
+ if ((specified != -1) && (now + 10*24*3600 < specified))
return 0;
tm->tm_mon = r->tm_mon;
tm->tm_mday = r->tm_mday;
/* mktime uses local timezone */
*timestamp = tm_to_time_t(&tm);
- if (*offset == -1)
- *offset = ((time_t)*timestamp - mktime(&tm)) / 60;
+ if (*offset == -1) {
+ time_t temp_time = mktime(&tm);
+ if ((time_t)*timestamp > temp_time) {
+ *offset = ((time_t)*timestamp - temp_time) / 60;
+ } else {
+ *offset = -(int)((temp_time - (time_t)*timestamp) / 60);
+ }
+ }
if (*timestamp == -1)
return -1;
const char *new = b;
struct strbuf name = STRBUF_INIT;
int pfx_length, sfx_length;
+ int pfx_adjust_for_slash;
int len_a = strlen(a);
int len_b = strlen(b);
int a_midlen, b_midlen;
old = a + len_a;
new = b + len_b;
sfx_length = 0;
- while (a <= old && b <= new && *old == *new) {
+ /*
+ * If there is a common prefix, it must end in a slash. In
+ * that case we let this loop run 1 into the prefix to see the
+ * same slash.
+ *
+ * If there is no common prefix, we cannot do this as it would
+ * underrun the input strings.
+ */
+ pfx_adjust_for_slash = (pfx_length ? 1 : 0);
+ while (a + pfx_length - pfx_adjust_for_slash <= old &&
+ b + pfx_length - pfx_adjust_for_slash <= new &&
+ *old == *new) {
if (*old == '/')
sfx_length = len_a - (old - a);
old--;
struct stat st;
if (ce_mode_s_ifmt == S_IFREG) {
- struct stream_filter *filter = get_stream_filter(path, ce->sha1);
+ struct stream_filter *filter = get_stream_filter(ce->name, ce->sha1);
if (filter &&
!streaming_write_entry(ce, path, filter,
state, to_tempfile,
static char *git_object_dir, *git_index_file, *git_graft_file;
/*
- * Repository-local GIT_* environment variables
- * Remember to update local_repo_env_size in cache.h when
- * the size of the list changes
+ * Repository-local GIT_* environment variables; see cache.h for details.
*/
-const char * const local_repo_env[LOCAL_REPO_ENV_SIZE + 1] = {
+const char * const local_repo_env[] = {
ALTERNATE_DB_ENVIRONMENT,
CONFIG_ENVIRONMENT,
CONFIG_DATA_ENVIRONMENT,
DB_ENVIRONMENT,
GIT_DIR_ENVIRONMENT,
GIT_WORK_TREE_ENVIRONMENT,
+ GIT_IMPLICIT_WORK_TREE_ENVIRONMENT,
GRAFT_ENVIRONMENT,
INDEX_ENVIRONMENT,
NO_REPLACE_OBJECTS_ENVIRONMENT,
+ GIT_PREFIX_ENVIRONMENT,
NULL
};
hashcpy(sha1, oe->idx.sha1);
} else if (!prefixcmp(p, "inline ")) {
inline_data = 1;
+ oe = NULL; /* not used with inline_data, but makes gcc happy */
p += strlen("inline"); /* advance to space */
} else {
if (get_sha1_hex(p, sha1))
* shallow and unshallow commands every time there
* is a block of have lines exchanged.
*/
- char line[1000];
- while (packet_read_line(fd, line, sizeof(line))) {
+ char *line;
+ while ((line = packet_read_line(fd, NULL))) {
if (!prefixcmp(line, "shallow "))
continue;
if (!prefixcmp(line, "unshallow "))
static enum ack_type get_ack(int fd, unsigned char *result_sha1)
{
- static char line[1000];
- int len = packet_read_line(fd, line, sizeof(line));
+ int len;
+ char *line = packet_read_line(fd, &len);
if (!len)
die("git fetch-pack: expected ACK/NAK, got EOF");
- if (line[len-1] == '\n')
- line[--len] = 0;
if (!strcmp(line, "NAK"))
return NAK;
if (!prefixcmp(line, "ACK ")) {
if (!get_sha1_hex(line+4, result_sha1)) {
+ if (len < 45)
+ return ACK;
if (strstr(line+45, "continue"))
return ACK_continue;
if (strstr(line+45, "common"))
send_sideband(fd, -1, buf->buf, buf->len, LARGE_PACKET_MAX);
packet_flush(fd);
} else
- safe_write(fd, buf->buf, buf->len);
+ write_or_die(fd, buf->buf, buf->len);
}
static void insert_one_alternate_ref(const struct ref *ref, void *unused)
state_len = req_buf.len;
if (args->depth > 0) {
- char line[1024];
+ char *line;
unsigned char sha1[20];
send_request(args, fd[1], &req_buf);
- while (packet_read_line(fd[0], line, sizeof(line))) {
+ while ((line = packet_read_line(fd[0], NULL))) {
if (!prefixcmp(line, "shallow ")) {
if (get_sha1_hex(line + 8, sha1))
die("invalid shallow line: %s", line);
action=yes
fi
- if test -f "$dotest/final-commit"
- then
- FIRSTLINE=$(sed 1q "$dotest/final-commit")
- else
- FIRSTLINE=""
- fi
-
if test $action = skip
then
go_next
stop_here $this
fi
+ if test -f "$dotest/final-commit"
+ then
+ FIRSTLINE=$(sed 1q "$dotest/final-commit")
+ else
+ FIRSTLINE=""
+ fi
+
say "$(eval_gettext "Applying: \$FIRSTLINE")"
case "$resolved" in
typedef long intptr_t;
typedef unsigned long uintptr_t;
#endif
+int get_st_mode_bits(const char *path, int *mode);
#if defined(__CYGWIN__)
#undef _XOPEN_SOURCE
#include <grp.h>
exit($status | ($status >> 8));
}
+sub use_wt_file
+{
+ my ($repo, $workdir, $file, $sha1, $symlinks) = @_;
+ my $null_sha1 = '0' x 40;
+
+ if ($sha1 eq $null_sha1) {
+ return 1;
+ } elsif (not $symlinks) {
+ return 0;
+ }
+
+ my $wt_sha1 = $repo->command_oneline('hash-object', "$workdir/$file");
+ return $sha1 eq $wt_sha1;
+}
+
sub setup_dir_diff
{
my ($repo, $workdir, $symlinks) = @_;
}
if ($rmode ne $null_mode) {
- if ($rsha1 ne $null_sha1) {
- $rindex .= "$rmode $rsha1\t$dst_path\0";
- } else {
+ if (use_wt_file($repo, $workdir, $dst_path, $rsha1, $symlinks)) {
push(@working_tree, $dst_path);
+ } else {
+ $rindex .= "$rmode $rsha1\t$dst_path\0";
}
}
}
delete($ENV{GIT_INDEX_FILE});
# Changes in the working tree need special treatment since they are
- # not part of the index
+ # not part of the index. Remove any trailing slash from $workdir
+ # before starting to avoid double slashes in symlink targets.
+ $workdir =~ s|/$||;
for my $file (@working_tree) {
my $dir = dirname($file);
unless (-d "$rdir/$dir") {
cd_to_toplevel
require_work_tree
-if ! test "$#" -eq 7
+if test $# != 7
then
echo "$LONG_USAGE"
exit 1
# Deleted in both or deleted in one and unchanged in the other
#
"$1.." | "$1.$1" | "$1$1.")
- if [ "$2" ]; then
+ if test -n "$2"
+ then
echo "Removing $4"
else
# read-tree checked that index matches HEAD already,
# we do not have it in the index, though.
exec git update-index --remove -- "$4"
fi
- if test -f "$4"; then
+ if test -f "$4"
+ then
rm -f -- "$4" &&
rmdir -p "$(expr "z$4" : 'z\(.*\)/')" 2>/dev/null || :
fi &&
echo "Adding $4"
if test -f "$4"
then
- echo "ERROR: untracked $4 is overwritten by the merge."
+ echo "ERROR: untracked $4 is overwritten by the merge." >&2
exit 1
fi
git update-index --add --cacheinfo "$7" "$3" "$4" &&
# Added in both, identically (check for same permissions).
#
".$3$2")
- if [ "$6" != "$7" ]; then
- echo "ERROR: File $4 added identically in both branches,"
- echo "ERROR: but permissions conflict $6->$7."
+ if test "$6" != "$7"
+ then
+ echo "ERROR: File $4 added identically in both branches," >&2
+ echo "ERROR: but permissions conflict $6->$7." >&2
exit 1
fi
echo "Adding $4"
case ",$6,$7," in
*,120000,*)
- echo "ERROR: $4: Not merging symbolic link changes."
+ echo "ERROR: $4: Not merging symbolic link changes." >&2
exit 1
;;
*,160000,*)
- echo "ERROR: $4: Not merging conflicting submodule changes."
+ echo "ERROR: $4: Not merging conflicting submodule changes." >&2
exit 1
;;
esac
- src2=`git-unpack-file $3`
+ src1=$(git-unpack-file $2)
+ src2=$(git-unpack-file $3)
case "$1" in
'')
echo "Added $4 in both, but differently."
- # This extracts OUR file in $orig, and uses git apply to
- # remove lines that are unique to ours.
- orig=`git-unpack-file $2`
- sz0=`wc -c <"$orig"`
- @@DIFF@@ -u -La/$orig -Lb/$orig $orig $src2 | git apply --no-add
- sz1=`wc -c <"$orig"`
-
- # If we do not have enough common material, it is not
- # worth trying two-file merge using common subsections.
- expr $sz0 \< $sz1 \* 2 >/dev/null || : >$orig
+ orig=$(git-unpack-file $2)
+ create_virtual_base "$orig" "$src2"
;;
*)
echo "Auto-merging $4"
- orig=`git-unpack-file $1`
+ orig=$(git-unpack-file $1)
;;
esac
- # Be careful for funny filename such as "-L" in "$4", which
- # would confuse "merge" greatly.
- src1=`git-unpack-file $2`
git merge-file "$src1" "$orig" "$src2"
ret=$?
msg=
- if [ $ret -ne 0 ]; then
+ if test $ret != 0 || test -z "$1"
+ then
msg='content conflict'
+ ret=1
fi
# Create the working tree file, using "our tree" version from the
git checkout-index -f --stage=2 -- "$4" && cat "$src1" >"$4" || exit 1
rm -f -- "$orig" "$src1" "$src2"
- if [ "$6" != "$7" ]; then
- if [ -n "$msg" ]; then
+ if test "$6" != "$7"
+ then
+ if test -n "$msg"
+ then
msg="$msg, "
fi
msg="${msg}permissions conflict: $5->$6,$7"
ret=1
fi
- if [ "$1" = '' ]; then
- ret=1
- fi
- if [ $ret -ne 0 ]; then
- echo "ERROR: $msg in $4"
+ if test $ret != 0
+ then
+ echo "ERROR: $msg in $4" >&2
exit 1
fi
exec git update-index -- "$4"
;;
*)
- echo "ERROR: $4: Not handling case $1 -> $2 -> $3"
+ echo "ERROR: $4: Not handling case $1 -> $2 -> $3" >&2
;;
esac
exit 1
real_cmd += cmd
return real_cmd
-def chdir(dir):
- # P4 uses the PWD environment variable rather than getcwd(). Since we're
- # not using the shell, we have to set it ourselves. This path could
- # be relative, so go there first, then figure out where we ended up.
- os.chdir(dir)
- os.environ['PWD'] = os.getcwd()
+def chdir(path, is_client_path=False):
+ """Do chdir to the given path, and set the PWD environment
+ variable for use by P4. It does not look at getcwd() output.
+ Since we're not using the shell, it is necessary to set the
+ PWD environment variable explicitly.
+
+ Normally, expand the path to force it to be absolute. This
+ addresses the use of relative path names inside P4 settings,
+ e.g. P4CONFIG=.p4config. P4 does not simply open the filename
+ as given; it looks for .p4config using PWD.
+
+ If is_client_path, the path was handed to us directly by p4,
+ and may be a symbolic link. Do not call os.getcwd() in this
+ case, because it will cause p4 to think that PWD is not inside
+ the client path.
+ """
+
+ os.chdir(path)
+ if not is_client_path:
+ path = os.getcwd()
+ os.environ['PWD'] = path
def die(msg):
if verbose:
new_client_dir = True
os.makedirs(self.clientPath)
- chdir(self.clientPath)
+ chdir(self.clientPath, is_client_path=True)
if self.dry_run:
print "Would synchronize p4 checkout in %s" % self.clientPath
else:
merge_name=$(git fmt-merge-msg $log_arg <"$GIT_DIR/FETCH_HEAD") || exit
case "$rebase" in
true)
- eval="git-rebase $diffstat $strategy_args $merge_args"
+ eval="git-rebase $diffstat $strategy_args $merge_args $verbosity"
eval="$eval --onto $merge_head ${oldremoteref:-$merge_head}"
;;
*)
unset $(git rev-parse --local-env-vars)
}
+# Generate a virtual base file for a two-file merge. Uses git apply to
+# remove lines from $1 that are not in $2, leaving only common lines.
+create_virtual_base() {
+ sz0=$(wc -c <"$1")
+ @@DIFF@@ -u -La/"$1" -Lb/"$1" "$1" "$2" | git apply --no-add
+ sz1=$(wc -c <"$1")
+
+ # If we do not have enough common material, it is not
+ # worth trying two-file merge using common subsections.
+ expr $sz0 \< $sz1 \* 2 >/dev/null || : >"$1"
+}
+
# Platform specific tweaks to work around some commands
case $(uname -s) in
USAGE="[--quiet] add [-b <branch>] [-f|--force] [--name <name>] [--reference <repository>] [--] <repository> [<path>]
or: $dashless [--quiet] status [--cached] [--recursive] [--] [<path>...]
or: $dashless [--quiet] init [--] [<path>...]
+ or: $dashless [--quiet] deinit [-f|--force] [--] <path>...
or: $dashless [--quiet] update [--init] [--remote] [-N|--no-fetch] [-f|--force] [--rebase] [--reference <repository>] [--merge] [--recursive] [--] [<path>...]
or: $dashless [--quiet] summary [--cached|--files] [--summary-limit <n>] [commit] [--] [<path>...]
or: $dashless [--quiet] foreach [--recursive] <command>
done
}
+#
+# Unregister submodules from .git/config and remove their work tree
+#
+# $@ = requested paths (use '.' to deinit all submodules)
+#
+cmd_deinit()
+{
+ # parse $args after "submodule ... deinit".
+ while test $# -ne 0
+ do
+ case "$1" in
+ -f|--force)
+ force=$1
+ ;;
+ -q|--quiet)
+ GIT_QUIET=1
+ ;;
+ --)
+ shift
+ break
+ ;;
+ -*)
+ usage
+ ;;
+ *)
+ break
+ ;;
+ esac
+ shift
+ done
+
+ if test $# = 0
+ then
+ die "$(eval_gettext "Use '.' if you really want to deinitialize all submodules")"
+ fi
+
+ module_list "$@" |
+ while read mode sha1 stage sm_path
+ do
+ die_if_unmatched "$mode"
+ name=$(module_name "$sm_path") || exit
+
+ # Remove the submodule work tree (unless the user already did it)
+ if test -d "$sm_path"
+ then
+ # Protect submodules containing a .git directory
+ if test -d "$sm_path/.git"
+ then
+ echo >&2 "$(eval_gettext "Submodule work tree '\$sm_path' contains a .git directory")"
+ die "$(eval_gettext "(use 'rm -rf' if you really want to remove it including all of its history)")"
+ fi
+
+ if test -z "$force"
+ then
+ git rm -n "$sm_path" ||
+ die "$(eval_gettext "Submodule work tree '\$sm_path' contains local modifications; use '-f' to discard them")"
+ fi
+ rm -rf "$sm_path" || say "$(eval_gettext "Could not remove submodule work tree '\$sm_path'")"
+ fi
+
+ mkdir "$sm_path" || say "$(eval_gettext "Could not create empty submodule directory '\$sm_path'")"
+
+ # Remove the .git/config entries (unless the user already did it)
+ if test -n "$(git config --get-regexp submodule."$name\.")"
+ then
+ # Remove the whole section so we have a clean state when
+ # the user later decides to init this submodule again
+ url=$(git config submodule."$name".url)
+ git config --remove-section submodule."$name" 2>/dev/null &&
+ say "$(eval_gettext "Submodule '\$name' (\$url) unregistered for path '\$sm_path'")"
+ fi
+ done
+}
+
#
# Update each submodule path to correct revision, using clone and checkout as needed
#
while test $# != 0 && test -z "$command"
do
case "$1" in
- add | foreach | init | update | status | summary | sync)
+ add | foreach | init | deinit | update | status | summary | sync)
command=$1
;;
-q|--quiet)
static char git_dir[PATH_MAX+1];
is_bare_repository_cfg = 1;
setenv(GIT_DIR_ENVIRONMENT, getcwd(git_dir, sizeof(git_dir)), 0);
+ setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
if (envchanged)
*envchanged = 1;
} else if (!strcmp(cmd, "-c")) {
for (p = opt->header_list; p; p = p->next) {
if (p->token != GREP_PATTERN_HEAD)
die("bug: a non-header pattern in grep header list.");
- if (p->field < 0 || GREP_HEADER_FIELD_MAX <= p->field)
+ if (p->field < GREP_HEADER_FIELD_MIN ||
+ GREP_HEADER_FIELD_MAX <= p->field)
die("bug: unknown header field %d", p->field);
compile_regexp(p, opt);
}
};
enum grep_header_field {
- GREP_HEADER_AUTHOR = 0,
+ GREP_HEADER_FIELD_MIN = 0,
+ GREP_HEADER_AUTHOR = GREP_HEADER_FIELD_MIN,
GREP_HEADER_COMMITTER,
GREP_HEADER_REFLOG,
if (n >= sizeof(buffer))
die("protocol error: impossibly long line");
- safe_write(fd, buffer, n);
+ write_or_die(fd, buffer, n);
}
static void http_status(unsigned code, const char *msg)
static void end_headers(void)
{
- safe_write(1, "\r\n", 2);
+ write_or_die(1, "\r\n", 2);
}
__attribute__((format (printf, 1, 2)))
hdr_int(content_length, buf->len);
hdr_str(content_type, type);
end_headers();
- safe_write(1, buf->buf, buf->len);
+ write_or_die(1, buf->buf, buf->len);
}
static void send_local_file(const char *the_type, const char *name)
die_errno("Cannot read '%s'", p);
if (!n)
break;
- safe_write(1, buf, n);
+ write_or_die(1, buf, n);
}
close(fd);
free(buf);
#include "url.h"
#include "credential.h"
#include "version.h"
+#include "pkt-line.h"
int active_requests;
int http_is_verbose;
#include "string-list.h"
#include "color.h"
#include "gpg-interface.h"
+#include "sequencer.h"
struct decoration name_decoration = { "object names" };
putchar(')');
}
-/*
- * Search for "^[-A-Za-z]+: [^@]+@" pattern. It usually matches
- * Signed-off-by: and Acked-by: lines.
- */
-static int detect_any_signoff(char *letter, int size)
-{
- char *cp;
- int seen_colon = 0;
- int seen_at = 0;
- int seen_name = 0;
- int seen_head = 0;
-
- cp = letter + size;
- while (letter <= --cp && *cp == '\n')
- continue;
-
- while (letter <= cp) {
- char ch = *cp--;
- if (ch == '\n')
- break;
-
- if (!seen_at) {
- if (ch == '@')
- seen_at = 1;
- continue;
- }
- if (!seen_colon) {
- if (ch == '@')
- return 0;
- else if (ch == ':')
- seen_colon = 1;
- else
- seen_name = 1;
- continue;
- }
- if (('A' <= ch && ch <= 'Z') ||
- ('a' <= ch && ch <= 'z') ||
- ch == '-') {
- seen_head = 1;
- continue;
- }
- /* no empty last line doesn't match */
- return 0;
- }
- return seen_head && seen_name;
-}
-
-static void append_signoff(struct strbuf *sb, const char *signoff)
-{
- static const char signed_off_by[] = "Signed-off-by: ";
- size_t signoff_len = strlen(signoff);
- int has_signoff = 0;
- char *cp;
-
- cp = sb->buf;
-
- /* First see if we already have the sign-off by the signer */
- while ((cp = strstr(cp, signed_off_by))) {
-
- has_signoff = 1;
-
- cp += strlen(signed_off_by);
- if (cp + signoff_len >= sb->buf + sb->len)
- break;
- if (strncmp(cp, signoff, signoff_len))
- continue;
- if (!isspace(cp[signoff_len]))
- continue;
- /* we already have him */
- return;
- }
-
- if (!has_signoff)
- has_signoff = detect_any_signoff(sb->buf, sb->len);
-
- if (!has_signoff)
- strbuf_addch(sb, '\n');
-
- strbuf_addstr(sb, signed_off_by);
- strbuf_add(sb, signoff, signoff_len);
- strbuf_addch(sb, '\n');
-}
-
static unsigned int digits_in_number(unsigned int number)
{
unsigned int i = 10, result = 1;
/*
* And then the pretty-printed message itself
*/
- if (ctx.need_8bit_cte >= 0)
- ctx.need_8bit_cte = has_non_ascii(opt->add_signoff);
+ if (ctx.need_8bit_cte >= 0 && opt->add_signoff)
+ ctx.need_8bit_cte =
+ has_non_ascii(fmt_name(getenv("GIT_COMMITTER_NAME"),
+ getenv("GIT_COMMITTER_EMAIL")));
ctx.date_mode = opt->date_mode;
ctx.date_mode_explicit = opt->date_mode_explicit;
ctx.abbrev = opt->diffopt.abbrev;
pretty_print_commit(&ctx, commit, &msgbuf);
if (opt->add_signoff)
- append_signoff(&msgbuf, opt->add_signoff);
+ append_signoff(&msgbuf, 0, APPEND_SIGNOFF_DEDUP);
if ((ctx.fmt != CMIT_FMT_USERFORMAT) &&
ctx.notes_message && *ctx.notes_message) {
{
int showed_log;
struct commit_list *parents;
- unsigned const char *sha1 = commit->object.sha1;
+ unsigned const char *sha1;
if (!opt->diff && !DIFF_OPT_TST(&opt->diffopt, EXIT_WITH_STATUS))
return 0;
+ parse_commit(commit);
+ sha1 = commit->tree->object.sha1;
+
/* Root commit? */
parents = commit->parents;
if (!parents) {
* parent, showing summary diff of the others
* we merged _in_.
*/
- diff_tree_sha1(parents->item->object.sha1, sha1, "", &opt->diffopt);
+ parse_commit(parents->item);
+ diff_tree_sha1(parents->item->tree->object.sha1,
+ sha1, "", &opt->diffopt);
log_tree_diff_flush(opt);
return !opt->loginfo;
}
for (;;) {
struct commit *parent = parents->item;
- diff_tree_sha1(parent->object.sha1, sha1, "", &opt->diffopt);
+ parse_commit(parent);
+ diff_tree_sha1(parent->tree->object.sha1,
+ sha1, "", &opt->diffopt);
log_tree_diff_flush(opt);
showed_log |= !opt->loginfo;
return score;
}
+static int base_name_entries_compare(const struct name_entry *a,
+ const struct name_entry *b)
+{
+ return base_name_compare(a->path, tree_entry_len(a), a->mode,
+ b->path, tree_entry_len(b), b->mode);
+}
+
/*
* Inspect two trees, and give a score that tells how similar they are.
*/
if (type != OBJ_TREE)
die("%s is not a tree", sha1_to_hex(hash2));
init_tree_desc(&two, two_buf, size);
- while (one.size | two.size) {
- const unsigned char *elem1 = elem1;
- const unsigned char *elem2 = elem2;
- const char *path1 = path1;
- const char *path2 = path2;
- unsigned mode1 = mode1;
- unsigned mode2 = mode2;
+ for (;;) {
+ struct name_entry e1, e2;
+ int got_entry_from_one = tree_entry(&one, &e1);
+ int got_entry_from_two = tree_entry(&two, &e2);
int cmp;
- if (one.size)
- elem1 = tree_entry_extract(&one, &path1, &mode1);
- if (two.size)
- elem2 = tree_entry_extract(&two, &path2, &mode2);
-
- if (!one.size) {
- /* two has more entries */
- score += score_missing(mode2, path2);
- update_tree_entry(&two);
- continue;
- }
- if (!two.size) {
+ if (got_entry_from_one && got_entry_from_two)
+ cmp = base_name_entries_compare(&e1, &e2);
+ else if (got_entry_from_one)
/* two lacks this entry */
- score += score_missing(mode1, path1);
- update_tree_entry(&one);
- continue;
- }
- cmp = base_name_compare(path1, strlen(path1), mode1,
- path2, strlen(path2), mode2);
- if (cmp < 0) {
+ cmp = -1;
+ else if (got_entry_from_two)
+ /* two has more entries */
+ cmp = 1;
+ else
+ break;
+
+ if (cmp < 0)
/* path1 does not appear in two */
- score += score_missing(mode1, path1);
- update_tree_entry(&one);
- continue;
- }
- else if (cmp > 0) {
+ score += score_missing(e1.mode, e1.path);
+ else if (cmp > 0)
/* path2 does not appear in one */
- score += score_missing(mode2, path2);
- update_tree_entry(&two);
- continue;
- }
- else if (hashcmp(elem1, elem2))
+ score += score_missing(e2.mode, e2.path);
+ else if (hashcmp(e1.sha1, e2.sha1))
/* they are different */
- score += score_differs(mode1, mode2, path1);
+ score += score_differs(e1.mode, e2.mode, e1.path);
else
/* same subtree or blob */
- score += score_matches(mode1, mode2, path1);
- update_tree_entry(&one);
- update_tree_entry(&two);
+ score += score_matches(e1.mode, e2.mode, e1.path);
}
free(one_buf);
free(two_buf);
merge_cmd () {
touch "$BACKUP"
- $base_present || >"$BASE"
- "$merge_tool_path" "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
+ if ! $base_present
+ then
+ cp -- "$LOCAL" "$BASE"
+ create_virtual_base "$BASE" "$REMOTE"
+ fi
+ "$merge_tool_path" "$BASE" "$REMOTE" "$LOCAL" "$MERGED"
check_unchanged
}
return hash;
}
-static void hash_index_entry_directories(struct index_state *istate, struct cache_entry *ce)
+struct dir_entry {
+ struct dir_entry *next;
+ struct dir_entry *parent;
+ struct cache_entry *ce;
+ int nr;
+ unsigned int namelen;
+};
+
+static struct dir_entry *find_dir_entry(struct index_state *istate,
+ const char *name, unsigned int namelen)
+{
+ unsigned int hash = hash_name(name, namelen);
+ struct dir_entry *dir;
+
+ for (dir = lookup_hash(hash, &istate->dir_hash); dir; dir = dir->next)
+ if (dir->namelen == namelen &&
+ !strncasecmp(dir->ce->name, name, namelen))
+ return dir;
+ return NULL;
+}
+
+static struct dir_entry *hash_dir_entry(struct index_state *istate,
+ struct cache_entry *ce, int namelen)
{
/*
* Throw each directory component in the hash for quick lookup
* during a git status. Directory components are stored with their
* closing slash. Despite submodules being a directory, they never
* reach this point, because they are stored without a closing slash
- * in the cache.
+ * in index_state.name_hash (as ordinary cache_entries).
*
- * Note that the cache_entry stored with the directory does not
- * represent the directory itself. It is a pointer to an existing
- * filename, and its only purpose is to represent existence of the
- * directory in the cache. It is very possible multiple directory
- * hash entries may point to the same cache_entry.
+ * Note that the cache_entry stored with the dir_entry merely
+ * supplies the name of the directory (up to dir_entry.namelen). We
+ * track the number of 'active' files in a directory in dir_entry.nr,
+ * so we can tell if the directory is still relevant, e.g. for git
+ * status. However, if cache_entries are removed, we cannot pinpoint
+ * an exact cache_entry that's still active. It is very possible that
+ * multiple dir_entries point to the same cache_entry.
*/
- unsigned int hash;
- void **pos;
+ struct dir_entry *dir;
+
+ /* get length of parent directory */
+ while (namelen > 0 && !is_dir_sep(ce->name[namelen - 1]))
+ namelen--;
+ if (namelen <= 0)
+ return NULL;
+
+ /* lookup existing entry for that directory */
+ dir = find_dir_entry(istate, ce->name, namelen);
+ if (!dir) {
+ /* not found, create it and add to hash table */
+ void **pdir;
+ unsigned int hash = hash_name(ce->name, namelen);
- const char *ptr = ce->name;
- while (*ptr) {
- while (*ptr && *ptr != '/')
- ++ptr;
- if (*ptr == '/') {
- ++ptr;
- hash = hash_name(ce->name, ptr - ce->name);
- pos = insert_hash(hash, ce, &istate->name_hash);
- if (pos) {
- ce->dir_next = *pos;
- *pos = ce;
- }
+ dir = xcalloc(1, sizeof(struct dir_entry));
+ dir->namelen = namelen;
+ dir->ce = ce;
+
+ pdir = insert_hash(hash, dir, &istate->dir_hash);
+ if (pdir) {
+ dir->next = *pdir;
+ *pdir = dir;
}
+
+ /* recursively add missing parent directories */
+ dir->parent = hash_dir_entry(istate, ce, namelen - 1);
}
+ return dir;
+}
+
+static void add_dir_entry(struct index_state *istate, struct cache_entry *ce)
+{
+ /* Add reference to the directory entry (and parents if 0). */
+ struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
+ while (dir && !(dir->nr++))
+ dir = dir->parent;
+}
+
+static void remove_dir_entry(struct index_state *istate, struct cache_entry *ce)
+{
+ /*
+ * Release reference to the directory entry (and parents if 0).
+ *
+ * Note: we do not remove / free the entry because there's no
+ * hash.[ch]::remove_hash and dir->next may point to other entries
+ * that are still valid, so we must not free the memory.
+ */
+ struct dir_entry *dir = hash_dir_entry(istate, ce, ce_namelen(ce));
+ while (dir && dir->nr && !(--dir->nr))
+ dir = dir->parent;
}
static void hash_index_entry(struct index_state *istate, struct cache_entry *ce)
if (ce->ce_flags & CE_HASHED)
return;
ce->ce_flags |= CE_HASHED;
- ce->next = ce->dir_next = NULL;
+ ce->next = NULL;
hash = hash_name(ce->name, ce_namelen(ce));
pos = insert_hash(hash, ce, &istate->name_hash);
if (pos) {
*pos = ce;
}
- if (ignore_case)
- hash_index_entry_directories(istate, ce);
+ if (ignore_case && !(ce->ce_flags & CE_UNHASHED))
+ add_dir_entry(istate, ce);
}
static void lazy_init_name_hash(struct index_state *istate)
void add_name_hash(struct index_state *istate, struct cache_entry *ce)
{
+ /* if already hashed, add reference to directory entries */
+ if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_STATE_MASK)
+ add_dir_entry(istate, ce);
+
ce->ce_flags &= ~CE_UNHASHED;
if (istate->name_hash_initialized)
hash_index_entry(istate, ce);
}
+/*
+ * We don't actually *remove* it, we can just mark it invalid so that
+ * we won't find it in lookups.
+ *
+ * Not only would we have to search the lists (simple enough), but
+ * we'd also have to rehash other hash buckets in case this makes the
+ * hash bucket empty (common). So it's much better to just mark
+ * it.
+ */
+void remove_name_hash(struct index_state *istate, struct cache_entry *ce)
+{
+ /* if already hashed, release reference to directory entries */
+ if (ignore_case && (ce->ce_flags & CE_STATE_MASK) == CE_HASHED)
+ remove_dir_entry(istate, ce);
+
+ ce->ce_flags |= CE_UNHASHED;
+}
+
static int slow_same_name(const char *name1, int len1, const char *name2, int len2)
{
if (len1 != len2)
if (!icase)
return 0;
- /*
- * If the entry we're comparing is a filename (no trailing slash), then compare
- * the lengths exactly.
- */
- if (name[namelen - 1] != '/')
- return slow_same_name(name, namelen, ce->name, len);
-
- /*
- * For a directory, we point to an arbitrary cache_entry filename. Just
- * make sure the directory portion matches.
- */
- return slow_same_name(name, namelen, ce->name, namelen < len ? namelen : len);
+ return slow_same_name(name, namelen, ce->name, len);
}
struct cache_entry *index_name_exists(struct index_state *istate, const char *name, int namelen, int icase)
if (same_name(ce, name, namelen, icase))
return ce;
}
- if (icase && name[namelen - 1] == '/')
- ce = ce->dir_next;
- else
- ce = ce->next;
+ ce = ce->next;
}
/*
- * Might be a submodule. Despite submodules being directories,
+ * When looking for a directory (trailing '/'), it might be a
+ * submodule or a directory. Despite submodules being directories,
* they are stored in the name hash without a closing slash.
- * When ignore_case is 1, directories are stored in the name hash
- * with their closing slash.
+ * When ignore_case is 1, directories are stored in a separate hash
+ * table *with* their closing slash.
*
* The side effect of this storage technique is we have need to
+ * lookup the directory in a separate hash table, and if not found
* remove the slash from name and perform the lookup again without
* the slash. If a match is made, S_ISGITLINK(ce->mode) will be
* true.
*/
if (icase && name[namelen - 1] == '/') {
+ struct dir_entry *dir = find_dir_entry(istate, name, namelen);
+ if (dir && dir->nr)
+ return dir->ce;
+
ce = index_name_exists(istate, name, namelen - 1, icase);
if (ce && S_ISGITLINK(ce->ce_mode))
return ce;
}
return NULL;
}
+
+static int free_dir_entry(void *entry, void *unused)
+{
+ struct dir_entry *dir = entry;
+ while (dir) {
+ struct dir_entry *next = dir->next;
+ free(dir);
+ dir = next;
+ }
+ return 0;
+}
+
+void free_name_hash(struct index_state *istate)
+{
+ if (!istate->name_hash_initialized)
+ return;
+ istate->name_hash_initialized = 0;
+ if (ignore_case)
+ /* free directory entries */
+ for_each_hash(&istate->dir_hash, free_dir_entry, NULL);
+
+ free_hash(&istate->name_hash);
+ free_hash(&istate->dir_hash);
+}
return obj;
}
+struct object *parse_object_or_die(const unsigned char *sha1,
+ const char *name)
+{
+ struct object *o = parse_object(sha1);
+ if (o)
+ return o;
+
+ die(_("unable to parse object: %s"), name ? name : sha1_to_hex(sha1));
+}
+
struct object *parse_object(const unsigned char *sha1)
{
unsigned long size;
extern void *create_object(const unsigned char *sha1, int type, void *obj);
-/** Returns the object, having parsed it to find out what it is. **/
+/*
+ * Returns the object, having parsed it to find out what it is.
+ *
+ * Returns NULL if the object is missing or corrupt.
+ */
struct object *parse_object(const unsigned char *sha1);
+/*
+ * Like parse_object, but will die() instead of returning NULL. If the
+ * "name" parameter is not NULL, it is included in the error message
+ * (otherwise, the sha1 hex is given).
+ */
+struct object *parse_object_or_die(const unsigned char *sha1, const char *name);
+
/* Given the result of read_sha1_file(), returns the object after
* parsing it. eaten_p indicates if the object has a borrowed copy
* of buffer and the caller should not free() it.
int flags, void *cb_data)
{
struct pack_refs_cb_data *cb = cb_data;
+ struct object *o;
int is_tag_ref;
/* Do not pack the symbolic refs */
return 0;
fprintf(cb->refs_file, "%s %s\n", sha1_to_hex(sha1), path);
- if (is_tag_ref) {
- struct object *o = parse_object(sha1);
- if (o->type == OBJ_TAG) {
- o = deref_tag(o, path, 0);
- if (o)
- fprintf(cb->refs_file, "^%s\n",
- sha1_to_hex(o->sha1));
- }
+
+ o = parse_object_or_die(sha1, path);
+ if (o->type == OBJ_TAG) {
+ o = deref_tag(o, path, 0);
+ if (o)
+ fprintf(cb->refs_file, "^%s\n",
+ sha1_to_hex(o->sha1));
}
if ((cb->flags & PACK_REFS_PRUNE) && !do_not_prune(flags)) {
die_errno("unable to create ref-pack file structure");
/* perhaps other traits later as well */
- fprintf(cbdata.refs_file, "# pack-refs with: peeled \n");
+ fprintf(cbdata.refs_file, "# pack-refs with: peeled fully-peeled \n");
for_each_ref(handle_one_ref, &cbdata);
if (ferror(cbdata.refs_file))
#include "strbuf.h"
#include "string-list.h"
+#ifndef get_st_mode_bits
+/*
+ * The replacement lstat(2) we use on Cygwin is incomplete and
+ * may return wrong permission bits. Most of the time we do not care,
+ * but the callsites of this wrapper do care.
+ */
+int get_st_mode_bits(const char *path, int *mode)
+{
+ struct stat st;
+ if (lstat(path, &st) < 0)
+ return -1;
+ *mode = st.st_mode;
+ return 0;
+}
+#endif
+
static char bad_path[] = "/bad-path/";
static char *get_pathname(void)
int set_shared_perm(const char *path, int mode)
{
- struct stat st;
int tweak, shared, orig_mode;
if (!shared_repository) {
return 0;
}
if (!mode) {
- if (lstat(path, &st) < 0)
+ if (get_st_mode_bits(path, &mode) < 0)
return -1;
- mode = st.st_mode;
orig_mode = mode;
} else
orig_mode = 0;
my $full_url = add_path_to_url( $self->url, $path );
my ($ra, $reparented);
- if ($old_url =~ m#^svn(\+ssh)?://# ||
+ if ($old_url =~ m#^svn(\+\w+)?://# ||
($full_url =~ m#^https?://# &&
canonicalize_url($full_url) ne $full_url)) {
$_[0] = undef;
#include "cache.h"
#include "pkt-line.h"
+char packet_buffer[LARGE_PACKET_MAX];
static const char *packet_trace_prefix = "git";
static const char trace_key[] = "GIT_TRACE_PACKET";
strbuf_release(&out);
}
-/*
- * Write a packetized stream, where each line is preceded by
- * its length (including the header) as a 4-byte hex number.
- * A length of 'zero' means end of stream (and a length of 1-3
- * would be an error).
- *
- * This is all pretty stupid, but we use this packetized line
- * format to make a streaming format possible without ever
- * over-running the read buffers. That way we'll never read
- * into what might be the pack data (which should go to another
- * process entirely).
- *
- * The writing side could use stdio, but since the reading
- * side can't, we stay with pure read/write interfaces.
- */
-ssize_t safe_write(int fd, const void *buf, ssize_t n)
-{
- ssize_t nn = n;
- while (n) {
- int ret = xwrite(fd, buf, n);
- if (ret > 0) {
- buf = (char *) buf + ret;
- n -= ret;
- continue;
- }
- if (!ret)
- die("write error (disk full?)");
- die_errno("write error");
- }
- return nn;
-}
-
/*
* If we buffered things up above (we don't, but we should),
* we'd flush it here
void packet_flush(int fd)
{
packet_trace("0000", 4, 1);
- safe_write(fd, "0000", 4);
+ write_or_die(fd, "0000", 4);
}
void packet_buf_flush(struct strbuf *buf)
va_start(args, fmt);
n = format_packet(fmt, args);
va_end(args);
- safe_write(fd, buffer, n);
+ write_or_die(fd, buffer, n);
}
void packet_buf_write(struct strbuf *buf, const char *fmt, ...)
strbuf_add(buf, buffer, n);
}
-static int safe_read(int fd, void *buffer, unsigned size, int return_line_fail)
+static int get_packet_data(int fd, char **src_buf, size_t *src_size,
+ void *dst, unsigned size, int options)
{
- ssize_t ret = read_in_full(fd, buffer, size);
- if (ret < 0)
- die_errno("read error");
- else if (ret < size) {
- if (return_line_fail)
+ ssize_t ret;
+
+ if (fd >= 0 && src_buf && *src_buf)
+ die("BUG: multiple sources given to packet_read");
+
+ /* Read up to "size" bytes from our source, whatever it is. */
+ if (src_buf && *src_buf) {
+ ret = size < *src_size ? size : *src_size;
+ memcpy(dst, *src_buf, ret);
+ *src_buf += ret;
+ *src_size -= ret;
+ } else {
+ ret = read_in_full(fd, dst, size);
+ if (ret < 0)
+ die_errno("read error");
+ }
+
+ /* And complain if we didn't get enough bytes to satisfy the read. */
+ if (ret < size) {
+ if (options & PACKET_READ_GENTLE_ON_EOF)
return -1;
die("The remote end hung up unexpectedly");
return len;
}
-static int packet_read_internal(int fd, char *buffer, unsigned size, int return_line_fail)
+int packet_read(int fd, char **src_buf, size_t *src_len,
+ char *buffer, unsigned size, int options)
{
int len, ret;
char linelen[4];
- ret = safe_read(fd, linelen, 4, return_line_fail);
- if (return_line_fail && ret < 0)
+ ret = get_packet_data(fd, src_buf, src_len, linelen, 4, options);
+ if (ret < 0)
return ret;
len = packet_length(linelen);
if (len < 0)
len -= 4;
if (len >= size)
die("protocol error: bad line length %d", len);
- ret = safe_read(fd, buffer, len, return_line_fail);
- if (return_line_fail && ret < 0)
+ ret = get_packet_data(fd, src_buf, src_len, buffer, len, options);
+ if (ret < 0)
return ret;
+
+ if ((options & PACKET_READ_CHOMP_NEWLINE) &&
+ len && buffer[len-1] == '\n')
+ len--;
+
buffer[len] = 0;
packet_trace(buffer, len, 0);
return len;
}
-int packet_read(int fd, char *buffer, unsigned size)
+static char *packet_read_line_generic(int fd,
+ char **src, size_t *src_len,
+ int *dst_len)
{
- return packet_read_internal(fd, buffer, size, 1);
+ int len = packet_read(fd, src, src_len,
+ packet_buffer, sizeof(packet_buffer),
+ PACKET_READ_CHOMP_NEWLINE);
+ if (dst_len)
+ *dst_len = len;
+ return len ? packet_buffer : NULL;
}
-int packet_read_line(int fd, char *buffer, unsigned size)
+char *packet_read_line(int fd, int *len_p)
{
- return packet_read_internal(fd, buffer, size, 0);
+ return packet_read_line_generic(fd, NULL, NULL, len_p);
}
-int packet_get_line(struct strbuf *out,
- char **src_buf, size_t *src_len)
+char *packet_read_line_buf(char **src, size_t *src_len, int *dst_len)
{
- int len;
-
- if (*src_len < 4)
- return -1;
- len = packet_length(*src_buf);
- if (len < 0)
- return -1;
- if (!len) {
- *src_buf += 4;
- *src_len -= 4;
- packet_trace("0000", 4, 0);
- return 0;
- }
- if (*src_len < len)
- return -2;
-
- *src_buf += 4;
- *src_len -= 4;
- len -= 4;
-
- strbuf_add(out, *src_buf, len);
- *src_buf += len;
- *src_len -= len;
- packet_trace(out->buf, out->len, 0);
- return len;
+ return packet_read_line_generic(-1, src, src_len, dst_len);
}
#include "strbuf.h"
/*
- * Silly packetized line writing interface
+ * Write a packetized stream, where each line is preceded by
+ * its length (including the header) as a 4-byte hex number.
+ * A length of 'zero' means end of stream (and a length of 1-3
+ * would be an error).
+ *
+ * This is all pretty stupid, but we use this packetized line
+ * format to make a streaming format possible without ever
+ * over-running the read buffers. That way we'll never read
+ * into what might be the pack data (which should go to another
+ * process entirely).
+ *
+ * The writing side could use stdio, but since the reading
+ * side can't, we stay with pure read/write interfaces.
*/
void packet_flush(int fd);
void packet_write(int fd, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
void packet_buf_flush(struct strbuf *buf);
void packet_buf_write(struct strbuf *buf, const char *fmt, ...) __attribute__((format (printf, 2, 3)));
-int packet_read_line(int fd, char *buffer, unsigned size);
-int packet_read(int fd, char *buffer, unsigned size);
-int packet_get_line(struct strbuf *out, char **src_buf, size_t *src_len);
-ssize_t safe_write(int, const void *, ssize_t);
+/*
+ * Read a packetized line into the buffer, which must be at least size bytes
+ * long. The return value specifies the number of bytes read into the buffer.
+ *
+ * If src_buffer is not NULL (and nor is *src_buffer), it should point to a
+ * buffer containing the packet data to parse, of at least *src_len bytes.
+ * After the function returns, src_buf will be incremented and src_len
+ * decremented by the number of bytes consumed.
+ *
+ * If src_buffer (or *src_buffer) is NULL, then data is read from the
+ * descriptor "fd".
+ *
+ * If options does not contain PACKET_READ_GENTLE_ON_EOF, we will die under any
+ * of the following conditions:
+ *
+ * 1. Read error from descriptor.
+ *
+ * 2. Protocol error from the remote (e.g., bogus length characters).
+ *
+ * 3. Receiving a packet larger than "size" bytes.
+ *
+ * 4. Truncated output from the remote (e.g., we expected a packet but got
+ * EOF, or we got a partial packet followed by EOF).
+ *
+ * If options does contain PACKET_READ_GENTLE_ON_EOF, we will not die on
+ * condition 4 (truncated input), but instead return -1. However, we will still
+ * die for the other 3 conditions.
+ *
+ * If options contains PACKET_READ_CHOMP_NEWLINE, a trailing newline (if
+ * present) is removed from the buffer before returning.
+ */
+#define PACKET_READ_GENTLE_ON_EOF (1u<<0)
+#define PACKET_READ_CHOMP_NEWLINE (1u<<1)
+int packet_read(int fd, char **src_buffer, size_t *src_len, char
+ *buffer, unsigned size, int options);
+
+/*
+ * Convenience wrapper for packet_read that is not gentle, and sets the
+ * CHOMP_NEWLINE option. The return value is NULL for a flush packet,
+ * and otherwise points to a static buffer (that may be overwritten by
+ * subsequent calls). If the size parameter is not NULL, the length of the
+ * packet is written to it.
+ */
+char *packet_read_line(int fd, int *size);
+
+/*
+ * Same as packet_read_line, but read from a buf rather than a descriptor;
+ * see packet_read for details on how src_* is used.
+ */
+char *packet_read_line_buf(char **src_buf, size_t *src_len, int *size);
+
+#define DEFAULT_PACKET_MAX 1000
+#define LARGE_PACKET_MAX 65520
+extern char packet_buffer[LARGE_PACKET_MAX];
#endif
return 0;
}
-static void add_rfc2047(struct strbuf *sb, const char *line, int len,
+static void add_rfc2047(struct strbuf *sb, const char *line, size_t len,
const char *encoding, enum rfc2047_type type)
{
static const int max_encoded_length = 76; /* per rfc2047 */
strbuf_grow(sb, len * 3 + strlen(encoding) + 100);
strbuf_addf(sb, "=?%s?q?", encoding);
line_len += strlen(encoding) + 5; /* 5 for =??q? */
- for (i = 0; i < len; i++) {
- unsigned ch = line[i] & 0xFF;
- int is_special = is_rfc2047_special(ch, type);
+
+ while (len) {
+ /*
+ * RFC 2047, section 5 (3):
+ *
+ * Each 'encoded-word' MUST represent an integral number of
+ * characters. A multi-octet character may not be split across
+ * adjacent 'encoded- word's.
+ */
+ const unsigned char *p = (const unsigned char *)line;
+ int chrlen = mbs_chrlen(&line, &len, encoding);
+ int is_special = (chrlen > 1) || is_rfc2047_special(*p, type);
+
+ /* "=%02X" * chrlen, or the byte itself */
+ const char *encoded_fmt = is_special ? "=%02X" : "%c";
+ int encoded_len = is_special ? 3 * chrlen : 1;
/*
* According to RFC 2047, we could encode the special character
* causes ' ' to be encoded as '=20', avoiding this problem.
*/
- if (line_len + 2 + (is_special ? 3 : 1) > max_encoded_length) {
+ if (line_len + encoded_len + 2 > max_encoded_length) {
+ /* It won't fit with trailing "?=" --- break the line */
strbuf_addf(sb, "?=\n =?%s?q?", encoding);
line_len = strlen(encoding) + 5 + 1; /* =??q? plus SP */
}
- if (is_special) {
- strbuf_addf(sb, "=%02X", ch);
- line_len += 3;
- } else {
- strbuf_addch(sb, ch);
- line_len++;
- }
+ for (i = 0; i < chrlen; i++)
+ strbuf_addf(sb, encoded_fmt, p[i]);
+ line_len += encoded_len;
}
strbuf_addstr(sb, "?=");
}
static int add_one_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
- struct object *object = parse_object(sha1);
+ struct object *object = parse_object_or_die(sha1, path);
struct rev_info *revs = (struct rev_info *)cb_data;
- if (!object)
- die("bad object ref: %s:%s", path, sha1_to_hex(sha1));
add_pending_object(revs, object, "");
return 0;
{
struct cache_entry *old = istate->cache[nr];
- remove_name_hash(old);
+ remove_name_hash(istate, old);
set_index_entry(istate, nr, ce);
istate->cache_changed = 1;
}
struct cache_entry *ce = istate->cache[pos];
record_resolve_undo(istate, ce);
- remove_name_hash(ce);
+ remove_name_hash(istate, ce);
istate->cache_changed = 1;
istate->cache_nr--;
if (pos >= istate->cache_nr)
for (i = j = 0; i < istate->cache_nr; i++) {
if (ce_array[i]->ce_flags & CE_REMOVE)
- remove_name_hash(ce_array[i]);
+ remove_name_hash(istate, ce_array[i]);
else
ce_array[j++] = ce_array[i];
}
istate->cache_changed = 0;
istate->timestamp.sec = 0;
istate->timestamp.nsec = 0;
- istate->name_hash_initialized = 0;
- free_hash(&istate->name_hash);
+ free_name_hash(istate);
cache_tree_free(&(istate->cache_tree));
istate->initialized = 0;
return line;
}
+/*
+ * Read f, which is a packed-refs file, into dir.
+ *
+ * A comment line of the form "# pack-refs with: " may contain zero or
+ * more traits. We interpret the traits as follows:
+ *
+ * No traits:
+ *
+ * Probably no references are peeled. But if the file contains a
+ * peeled value for a reference, we will use it.
+ *
+ * peeled:
+ *
+ * References under "refs/tags/", if they *can* be peeled, *are*
+ * peeled in this file. References outside of "refs/tags/" are
+ * probably not peeled even if they could have been, but if we find
+ * a peeled value for such a reference we will use it.
+ *
+ * fully-peeled:
+ *
+ * All references in the file that can be peeled are peeled.
+ * Inversely (and this is more important), any references in the
+ * file for which no peeled value is recorded is not peelable. This
+ * trait should typically be written alongside "peeled" for
+ * compatibility with older clients, but we do not require it
+ * (i.e., "peeled" is a no-op if "fully-peeled" is set).
+ */
static void read_packed_refs(FILE *f, struct ref_dir *dir)
{
struct ref_entry *last = NULL;
char refline[PATH_MAX];
- int flag = REF_ISPACKED;
+ enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
while (fgets(refline, sizeof(refline), f)) {
unsigned char sha1[20];
if (!strncmp(refline, header, sizeof(header)-1)) {
const char *traits = refline + sizeof(header) - 1;
- if (strstr(traits, " peeled "))
- flag |= REF_KNOWS_PEELED;
+ if (strstr(traits, " fully-peeled "))
+ peeled = PEELED_FULLY;
+ else if (strstr(traits, " peeled "))
+ peeled = PEELED_TAGS;
/* perhaps other traits later as well */
continue;
}
refname = parse_ref_line(refline, sha1);
if (refname) {
- last = create_ref_entry(refname, sha1, flag, 1);
+ last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
+ if (peeled == PEELED_FULLY ||
+ (peeled == PEELED_TAGS && !prefixcmp(refname, "refs/tags/")))
+ last->flag |= REF_KNOWS_PEELED;
add_ref(dir, last);
continue;
}
refline[0] == '^' &&
strlen(refline) == 42 &&
refline[41] == '\n' &&
- !get_sha1_hex(refline + 1, sha1))
+ !get_sha1_hex(refline + 1, sha1)) {
hashcpy(last->u.value.peeled, sha1);
+ /*
+ * Regardless of what the file header said,
+ * we definitely know the value of *this*
+ * reference:
+ */
+ last->flag |= REF_KNOWS_PEELED;
+ }
}
}
return 1;
}
-int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long ofs, void *cb_data)
+static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
+{
+ unsigned char osha1[20], nsha1[20];
+ char *email_end, *message;
+ unsigned long timestamp;
+ int tz;
+
+ /* old SP new SP name <email> SP time TAB msg LF */
+ if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' ||
+ get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' ||
+ get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' ||
+ !(email_end = strchr(sb->buf + 82, '>')) ||
+ email_end[1] != ' ' ||
+ !(timestamp = strtoul(email_end + 2, &message, 10)) ||
+ !message || message[0] != ' ' ||
+ (message[1] != '+' && message[1] != '-') ||
+ !isdigit(message[2]) || !isdigit(message[3]) ||
+ !isdigit(message[4]) || !isdigit(message[5]))
+ return 0; /* corrupt? */
+ email_end[1] = '\0';
+ tz = strtol(message + 1, NULL, 10);
+ if (message[6] != '\t')
+ message += 6;
+ else
+ message += 7;
+ return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data);
+}
+
+static char *find_beginning_of_line(char *bob, char *scan)
+{
+ while (bob < scan && *(--scan) != '\n')
+ ; /* keep scanning backwards */
+ /*
+ * Return either beginning of the buffer, or LF at the end of
+ * the previous line.
+ */
+ return scan;
+}
+
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data)
{
- const char *logfile;
- FILE *logfp;
struct strbuf sb = STRBUF_INIT;
- int ret = 0;
+ FILE *logfp;
+ long pos;
+ int ret = 0, at_tail = 1;
- logfile = git_path("logs/%s", refname);
- logfp = fopen(logfile, "r");
+ logfp = fopen(git_path("logs/%s", refname), "r");
if (!logfp)
return -1;
- if (ofs) {
- struct stat statbuf;
- if (fstat(fileno(logfp), &statbuf) ||
- statbuf.st_size < ofs ||
- fseek(logfp, -ofs, SEEK_END) ||
- strbuf_getwholeline(&sb, logfp, '\n')) {
- fclose(logfp);
- strbuf_release(&sb);
- return -1;
+ /* Jump to the end */
+ if (fseek(logfp, 0, SEEK_END) < 0)
+ return error("cannot seek back reflog for %s: %s",
+ refname, strerror(errno));
+ pos = ftell(logfp);
+ while (!ret && 0 < pos) {
+ int cnt;
+ size_t nread;
+ char buf[BUFSIZ];
+ char *endp, *scanp;
+
+ /* Fill next block from the end */
+ cnt = (sizeof(buf) < pos) ? sizeof(buf) : pos;
+ if (fseek(logfp, pos - cnt, SEEK_SET))
+ return error("cannot seek back reflog for %s: %s",
+ refname, strerror(errno));
+ nread = fread(buf, cnt, 1, logfp);
+ if (nread != 1)
+ return error("cannot read %d bytes from reflog for %s: %s",
+ cnt, refname, strerror(errno));
+ pos -= cnt;
+
+ scanp = endp = buf + cnt;
+ if (at_tail && scanp[-1] == '\n')
+ /* Looking at the final LF at the end of the file */
+ scanp--;
+ at_tail = 0;
+
+ while (buf < scanp) {
+ /*
+ * terminating LF of the previous line, or the beginning
+ * of the buffer.
+ */
+ char *bp;
+
+ bp = find_beginning_of_line(buf, scanp);
+
+ if (*bp != '\n') {
+ strbuf_splice(&sb, 0, 0, buf, endp - buf);
+ if (pos)
+ break; /* need to fill another block */
+ scanp = buf - 1; /* leave loop */
+ } else {
+ /*
+ * (bp + 1) thru endp is the beginning of the
+ * current line we have in sb
+ */
+ strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
+ scanp = bp;
+ endp = bp + 1;
+ }
+ ret = show_one_reflog_ent(&sb, fn, cb_data);
+ strbuf_reset(&sb);
+ if (ret)
+ break;
}
- }
- while (!strbuf_getwholeline(&sb, logfp, '\n')) {
- unsigned char osha1[20], nsha1[20];
- char *email_end, *message;
- unsigned long timestamp;
- int tz;
-
- /* old SP new SP name <email> SP time TAB msg LF */
- if (sb.len < 83 || sb.buf[sb.len - 1] != '\n' ||
- get_sha1_hex(sb.buf, osha1) || sb.buf[40] != ' ' ||
- get_sha1_hex(sb.buf + 41, nsha1) || sb.buf[81] != ' ' ||
- !(email_end = strchr(sb.buf + 82, '>')) ||
- email_end[1] != ' ' ||
- !(timestamp = strtoul(email_end + 2, &message, 10)) ||
- !message || message[0] != ' ' ||
- (message[1] != '+' && message[1] != '-') ||
- !isdigit(message[2]) || !isdigit(message[3]) ||
- !isdigit(message[4]) || !isdigit(message[5]))
- continue; /* corrupt? */
- email_end[1] = '\0';
- tz = strtol(message + 1, NULL, 10);
- if (message[6] != '\t')
- message += 6;
- else
- message += 7;
- ret = fn(osha1, nsha1, sb.buf + 82, timestamp, tz, message,
- cb_data);
- if (ret)
- break;
}
+ if (!ret && sb.len)
+ ret = show_one_reflog_ent(&sb, fn, cb_data);
+
fclose(logfp);
strbuf_release(&sb);
return ret;
int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data)
{
- return for_each_recent_reflog_ent(refname, fn, 0, cb_data);
-}
+ FILE *logfp;
+ struct strbuf sb = STRBUF_INIT;
+ int ret = 0;
+ logfp = fopen(git_path("logs/%s", refname), "r");
+ if (!logfp)
+ return -1;
+
+ while (!ret && !strbuf_getwholeline(&sb, logfp, '\n'))
+ ret = show_one_reflog_ent(&sb, fn, cb_data);
+ fclose(logfp);
+ strbuf_release(&sb);
+ return ret;
+}
/*
* Call fn for each reflog in the namespace indicated by name. name
* must be empty or end with '/'. Name will be used as a scratch
/* iterate over reflog entries */
typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
-int for_each_recent_reflog_ent(const char *refname, each_reflog_ent_fn fn, long, void *cb_data);
+int for_each_reflog_ent_reverse(const char *refname, each_reflog_ent_fn fn, void *cb_data);
/*
* Calls the specified function for each reflog file until it returns nonzero,
char *buf_alloc;
char *buf;
size_t len;
+ struct ref *refs;
unsigned proto_git : 1;
};
static struct discovery *last_discovery;
+static struct ref *parse_git_refs(struct discovery *heads, int for_push)
+{
+ struct ref *list = NULL;
+ get_remote_heads(-1, heads->buf, heads->len, &list,
+ for_push ? REF_NORMAL : 0, NULL);
+ return list;
+}
+
+static struct ref *parse_info_refs(struct discovery *heads)
+{
+ char *data, *start, *mid;
+ char *ref_name;
+ int i = 0;
+
+ struct ref *refs = NULL;
+ struct ref *ref = NULL;
+ struct ref *last_ref = NULL;
+
+ data = heads->buf;
+ start = NULL;
+ mid = data;
+ while (i < heads->len) {
+ if (!start) {
+ start = &data[i];
+ }
+ if (data[i] == '\t')
+ mid = &data[i];
+ if (data[i] == '\n') {
+ if (mid - start != 40)
+ die("%sinfo/refs not valid: is this a git repository?", url);
+ data[i] = 0;
+ ref_name = mid + 1;
+ ref = xmalloc(sizeof(struct ref) +
+ strlen(ref_name) + 1);
+ memset(ref, 0, sizeof(struct ref));
+ strcpy(ref->name, ref_name);
+ get_sha1_hex(start, ref->old_sha1);
+ if (!refs)
+ refs = ref;
+ if (last_ref)
+ last_ref->next = ref;
+ last_ref = ref;
+ start = NULL;
+ }
+ i++;
+ }
+
+ ref = alloc_ref("HEAD");
+ if (!http_fetch_ref(url, ref) &&
+ !resolve_remote_symref(ref, refs)) {
+ ref->next = refs;
+ refs = ref;
+ } else {
+ free(ref);
+ }
+
+ return refs;
+}
+
static void free_discovery(struct discovery *d)
{
if (d) {
if (d == last_discovery)
last_discovery = NULL;
free(d->buf_alloc);
+ free_refs(d->refs);
free(d);
}
}
-static struct discovery* discover_refs(const char *service)
+static struct discovery* discover_refs(const char *service, int for_push)
{
struct strbuf exp = STRBUF_INIT;
struct strbuf type = STRBUF_INIT;
if (maybe_smart &&
(5 <= last->len && last->buf[4] == '#') &&
!strbuf_cmp(&exp, &type)) {
+ char *line;
+
/*
* smart HTTP response; validate that the service
* pkt-line matches our request.
*/
- if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
- die("%s has invalid packet header", refs_url);
- if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
- strbuf_setlen(&buffer, buffer.len - 1);
+ line = packet_read_line_buf(&last->buf, &last->len, NULL);
strbuf_reset(&exp);
strbuf_addf(&exp, "# service=%s", service);
- if (strbuf_cmp(&exp, &buffer))
- die("invalid server response; got '%s'", buffer.buf);
+ if (strcmp(line, exp.buf))
+ die("invalid server response; got '%s'", line);
strbuf_release(&exp);
/* The header can include additional metadata lines, up
* until a packet flush marker. Ignore these now, but
* in the future we might start to scan them.
*/
- strbuf_reset(&buffer);
- while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
- strbuf_reset(&buffer);
+ while (packet_read_line_buf(&last->buf, &last->len, NULL))
+ ;
last->proto_git = 1;
}
+ if (last->proto_git)
+ last->refs = parse_git_refs(last, for_push);
+ else
+ last->refs = parse_info_refs(last);
+
free(refs_url);
strbuf_release(&exp);
strbuf_release(&type);
return last;
}
-static int write_discovery(int in, int out, void *data)
-{
- struct discovery *heads = data;
- int err = 0;
- if (write_in_full(out, heads->buf, heads->len) != heads->len)
- err = 1;
- close(out);
- return err;
-}
-
-static struct ref *parse_git_refs(struct discovery *heads, int for_push)
-{
- struct ref *list = NULL;
- struct async async;
-
- memset(&async, 0, sizeof(async));
- async.proc = write_discovery;
- async.data = heads;
- async.out = -1;
-
- if (start_async(&async))
- die("cannot start thread to parse advertised refs");
- get_remote_heads(async.out, &list,
- for_push ? REF_NORMAL : 0, NULL);
- close(async.out);
- if (finish_async(&async))
- die("ref parsing thread failed");
- return list;
-}
-
-static struct ref *parse_info_refs(struct discovery *heads)
-{
- char *data, *start, *mid;
- char *ref_name;
- int i = 0;
-
- struct ref *refs = NULL;
- struct ref *ref = NULL;
- struct ref *last_ref = NULL;
-
- data = heads->buf;
- start = NULL;
- mid = data;
- while (i < heads->len) {
- if (!start) {
- start = &data[i];
- }
- if (data[i] == '\t')
- mid = &data[i];
- if (data[i] == '\n') {
- if (mid - start != 40)
- die("%sinfo/refs not valid: is this a git repository?", url);
- data[i] = 0;
- ref_name = mid + 1;
- ref = xmalloc(sizeof(struct ref) +
- strlen(ref_name) + 1);
- memset(ref, 0, sizeof(struct ref));
- strcpy(ref->name, ref_name);
- get_sha1_hex(start, ref->old_sha1);
- if (!refs)
- refs = ref;
- if (last_ref)
- last_ref->next = ref;
- last_ref = ref;
- start = NULL;
- }
- i++;
- }
-
- ref = alloc_ref("HEAD");
- if (!http_fetch_ref(url, ref) &&
- !resolve_remote_symref(ref, refs)) {
- ref->next = refs;
- refs = ref;
- } else {
- free(ref);
- }
-
- return refs;
-}
-
static struct ref *get_refs(int for_push)
{
struct discovery *heads;
if (for_push)
- heads = discover_refs("git-receive-pack");
+ heads = discover_refs("git-receive-pack", for_push);
else
- heads = discover_refs("git-upload-pack");
+ heads = discover_refs("git-upload-pack", for_push);
- if (heads->proto_git)
- return parse_git_refs(heads, for_push);
- return parse_info_refs(heads);
+ return heads->refs;
}
static void output_refs(struct ref *refs)
}
printf("\n");
fflush(stdout);
- free_refs(refs);
}
struct rpc_state {
if (!avail) {
rpc->initial_buffer = 0;
- avail = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+ avail = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
if (!avail)
return 0;
rpc->pos = 0;
break;
}
- n = packet_read_line(rpc->out, buf, left);
+ n = packet_read(rpc->out, NULL, NULL, buf, left, 0);
if (!n)
break;
rpc->len += n;
rpc->hdr_accept = strbuf_detach(&buf, NULL);
while (!err) {
- int n = packet_read_line(rpc->out, rpc->buf, rpc->alloc);
+ int n = packet_read(rpc->out, NULL, NULL, rpc->buf, rpc->alloc, 0);
if (!n)
break;
rpc->pos = 0;
err = rpc_service(&rpc, heads);
if (rpc.result.len)
- safe_write(1, rpc.result.buf, rpc.result.len);
+ write_or_die(1, rpc.result.buf, rpc.result.len);
strbuf_release(&rpc.result);
strbuf_release(&preamble);
free(depth_arg);
static int fetch(int nr_heads, struct ref **to_fetch)
{
- struct discovery *d = discover_refs("git-upload-pack");
+ struct discovery *d = discover_refs("git-upload-pack", 0);
if (d->proto_git)
return fetch_git(d, nr_heads, to_fetch);
else
err = rpc_service(&rpc, heads);
if (rpc.result.len)
- safe_write(1, rpc.result.buf, rpc.result.len);
+ write_or_die(1, rpc.result.buf, rpc.result.len);
strbuf_release(&rpc.result);
free(argv);
return err;
static int push(int nr_spec, char **specs)
{
- struct discovery *heads = discover_refs("git-receive-pack");
+ struct discovery *heads = discover_refs("git-receive-pack", 1);
int ret;
if (heads->proto_git)
return tail;
}
+struct tips {
+ struct commit **tip;
+ int nr, alloc;
+};
+
+static void add_to_tips(struct tips *tips, const unsigned char *sha1)
+{
+ struct commit *commit;
+
+ if (is_null_sha1(sha1))
+ return;
+ commit = lookup_commit_reference_gently(sha1, 1);
+ if (!commit || (commit->object.flags & TMP_MARK))
+ return;
+ commit->object.flags |= TMP_MARK;
+ ALLOC_GROW(tips->tip, tips->nr + 1, tips->alloc);
+ tips->tip[tips->nr++] = commit;
+}
+
+static void add_missing_tags(struct ref *src, struct ref **dst, struct ref ***dst_tail)
+{
+ struct string_list dst_tag = STRING_LIST_INIT_NODUP;
+ struct string_list src_tag = STRING_LIST_INIT_NODUP;
+ struct string_list_item *item;
+ struct ref *ref;
+ struct tips sent_tips;
+
+ /*
+ * Collect everything we know they would have at the end of
+ * this push, and collect all tags they have.
+ */
+ memset(&sent_tips, 0, sizeof(sent_tips));
+ for (ref = *dst; ref; ref = ref->next) {
+ if (ref->peer_ref &&
+ !is_null_sha1(ref->peer_ref->new_sha1))
+ add_to_tips(&sent_tips, ref->peer_ref->new_sha1);
+ else
+ add_to_tips(&sent_tips, ref->old_sha1);
+ if (!prefixcmp(ref->name, "refs/tags/"))
+ string_list_append(&dst_tag, ref->name);
+ }
+ clear_commit_marks_many(sent_tips.nr, sent_tips.tip, TMP_MARK);
+
+ sort_string_list(&dst_tag);
+
+ /* Collect tags they do not have. */
+ for (ref = src; ref; ref = ref->next) {
+ if (prefixcmp(ref->name, "refs/tags/"))
+ continue; /* not a tag */
+ if (string_list_has_string(&dst_tag, ref->name))
+ continue; /* they already have it */
+ if (sha1_object_info(ref->new_sha1, NULL) != OBJ_TAG)
+ continue; /* be conservative */
+ item = string_list_append(&src_tag, ref->name);
+ item->util = ref;
+ }
+ string_list_clear(&dst_tag, 0);
+
+ /*
+ * At this point, src_tag lists tags that are missing from
+ * dst, and sent_tips lists the tips we are pushing or those
+ * that we know they already have. An element in the src_tag
+ * that is an ancestor of any of the sent_tips needs to be
+ * sent to the other side.
+ */
+ if (sent_tips.nr) {
+ for_each_string_list_item(item, &src_tag) {
+ struct ref *ref = item->util;
+ struct ref *dst_ref;
+ struct commit *commit;
+
+ if (is_null_sha1(ref->new_sha1))
+ continue;
+ commit = lookup_commit_reference_gently(ref->new_sha1, 1);
+ if (!commit)
+ /* not pushing a commit, which is not an error */
+ continue;
+
+ /*
+ * Is this tag, which they do not have, reachable from
+ * any of the commits we are sending?
+ */
+ if (!in_merge_bases_many(commit, sent_tips.nr, sent_tips.tip))
+ continue;
+
+ /* Add it in */
+ dst_ref = make_linked_ref(ref->name, dst_tail);
+ hashcpy(dst_ref->new_sha1, ref->new_sha1);
+ dst_ref->peer_ref = copy_ref(ref);
+ }
+ }
+ string_list_clear(&src_tag, 0);
+ free(sent_tips.tip);
+}
+
/*
* Given the set of refs the local repository has, the set of refs the
* remote repository has, and the refspec used for push, determine
const struct refspec *pat = NULL;
char *dst_name;
- if (ref->peer_ref)
- continue;
-
dst_name = get_ref_match(rs, nr_refspec, ref, send_mirror, FROM_SRC, &pat);
if (!dst_name)
continue;
free_name:
free(dst_name);
}
+
+ if (flags & MATCH_REFS_FOLLOW_TAGS)
+ add_missing_tags(src, dst, &dst_tail);
+
if (send_prune) {
/* check for missing refs on the remote */
for (ref = *dst; ref; ref = ref->next) {
MATCH_REFS_NONE = 0,
MATCH_REFS_ALL = (1 << 0),
MATCH_REFS_MIRROR = (1 << 1),
- MATCH_REFS_PRUNE = (1 << 2)
+ MATCH_REFS_PRUNE = (1 << 2),
+ MATCH_REFS_FOLLOW_TAGS = (1 << 3)
};
/* Reporting of tracking info */
* Does the destination list contain entries with a date
* before the source list? Definitely _not_ done.
*/
- if (date < src->item->date)
+ if (date <= src->item->date)
return SLOP;
/*
return st;
}
+static void remove_treesame_parents(struct commit *commit)
+{
+ struct commit_list **pp, *p;
+
+ pp = &commit->parents;
+ while ((p = *pp) != NULL) {
+ struct commit *parent = p->item;
+ if (parent->object.flags & TREESAME) {
+ *pp = p->next;
+ free(p);
+ continue;
+ }
+ pp = &p->next;
+ }
+}
+
static struct commit_list **simplify_one(struct rev_info *revs, struct commit *commit, struct commit_list **tail)
{
struct commit_list *p;
if (revs->first_parent_only)
break;
}
- if (!revs->first_parent_only)
- cnt = remove_duplicate_parents(commit);
- else
+
+ if (revs->first_parent_only) {
cnt = 1;
+ } else {
+ /*
+ * A merge with a tree-same parent is useless
+ */
+ if (commit->parents && commit->parents->next)
+ remove_treesame_parents(commit);
+
+ cnt = remove_duplicate_parents(commit);
+ }
/*
* It is possible that we are a merge and one side branch
int reroll_count;
char *message_id;
struct string_list *ref_message_ids;
- const char *add_signoff;
+ int add_signoff;
const char *extra_headers;
const char *log_reencode;
const char *subject_prefix;
static int receive_status(int in, struct ref *refs)
{
struct ref *hint;
- char line[1000];
int ret = 0;
- int len = packet_read_line(in, line, sizeof(line));
- if (len < 10 || memcmp(line, "unpack ", 7))
+ char *line = packet_read_line(in, NULL);
+ if (prefixcmp(line, "unpack "))
return error("did not receive remote status");
- if (memcmp(line, "unpack ok\n", 10)) {
- char *p = line + strlen(line) - 1;
- if (*p == '\n')
- *p = '\0';
+ if (strcmp(line, "unpack ok")) {
error("unpack failed: %s", line + 7);
ret = -1;
}
while (1) {
char *refname;
char *msg;
- len = packet_read_line(in, line, sizeof(line));
- if (!len)
+ line = packet_read_line(in, NULL);
+ if (!line)
break;
- if (len < 3 ||
- (memcmp(line, "ok ", 3) && memcmp(line, "ng ", 3))) {
- fprintf(stderr, "protocol error: %s\n", line);
+ if (prefixcmp(line, "ok ") && prefixcmp(line, "ng ")) {
+ error("invalid ref status from remote: %s", line);
ret = -1;
break;
}
- line[strlen(line)-1] = '\0';
refname = line + 3;
msg = strchr(refname, ' ');
if (msg)
send_sideband(out, -1, req_buf.buf, req_buf.len, LARGE_PACKET_MAX);
}
} else {
- safe_write(out, req_buf.buf, req_buf.len);
+ write_or_die(out, req_buf.buf, req_buf.len);
packet_flush(out);
}
strbuf_release(&req_buf);
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
const char sign_off_header[] = "Signed-off-by: ";
+static const char cherry_picked_prefix[] = "(cherry picked from commit ";
+
+static int is_rfc2822_line(const char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++) {
+ int ch = buf[i];
+ if (ch == ':')
+ return 1;
+ if (!isalnum(ch) && ch != '-')
+ break;
+ }
+
+ return 0;
+}
+
+static int is_cherry_picked_from_line(const char *buf, int len)
+{
+ /*
+ * We only care that it looks roughly like (cherry picked from ...)
+ */
+ return len > strlen(cherry_picked_prefix) + 1 &&
+ !prefixcmp(buf, cherry_picked_prefix) && buf[len - 1] == ')';
+}
+
+/*
+ * Returns 0 for non-conforming footer
+ * Returns 1 for conforming footer
+ * Returns 2 when sob exists within conforming footer
+ * Returns 3 when sob exists within conforming footer as last entry
+ */
+static int has_conforming_footer(struct strbuf *sb, struct strbuf *sob,
+ int ignore_footer)
+{
+ char prev;
+ int i, k;
+ int len = sb->len - ignore_footer;
+ const char *buf = sb->buf;
+ int found_sob = 0;
+
+ /* footer must end with newline */
+ if (!len || buf[len - 1] != '\n')
+ return 0;
+
+ prev = '\0';
+ for (i = len - 1; i > 0; i--) {
+ char ch = buf[i];
+ if (prev == '\n' && ch == '\n') /* paragraph break */
+ break;
+ prev = ch;
+ }
+
+ /* require at least one blank line */
+ if (prev != '\n' || buf[i] != '\n')
+ return 0;
+
+ /* advance to start of last paragraph */
+ while (i < len - 1 && buf[i] == '\n')
+ i++;
+
+ for (; i < len; i = k) {
+ int found_rfc2822;
+
+ for (k = i; k < len && buf[k] != '\n'; k++)
+ ; /* do nothing */
+ k++;
+
+ found_rfc2822 = is_rfc2822_line(buf + i, k - i - 1);
+ if (found_rfc2822 && sob &&
+ !strncmp(buf + i, sob->buf, sob->len))
+ found_sob = k;
+
+ if (!(found_rfc2822 ||
+ is_cherry_picked_from_line(buf + i, k - i - 1)))
+ return 0;
+ }
+ if (found_sob == i)
+ return 3;
+ if (found_sob)
+ return 2;
+ return 1;
+}
static void remove_sequencer_state(void)
{
rollback_lock_file(&index_lock);
if (opts->signoff)
- append_signoff(msgbuf, 0);
+ append_signoff(msgbuf, 0, 0);
if (!clean) {
int i;
}
if (opts->record_origin) {
- strbuf_addstr(&msgbuf, "(cherry picked from commit ");
+ if (!has_conforming_footer(&msgbuf, NULL, 0))
+ strbuf_addch(&msgbuf, '\n');
+ strbuf_addstr(&msgbuf, cherry_picked_prefix);
strbuf_addstr(&msgbuf, sha1_to_hex(commit->object.sha1));
strbuf_addstr(&msgbuf, ")\n");
}
return pick_commits(todo_list, opts);
}
-static int ends_rfc2822_footer(struct strbuf *sb, int ignore_footer)
-{
- int ch;
- int hit = 0;
- int i, j, k;
- int len = sb->len - ignore_footer;
- int first = 1;
- const char *buf = sb->buf;
-
- for (i = len - 1; i > 0; i--) {
- if (hit && buf[i] == '\n')
- break;
- hit = (buf[i] == '\n');
- }
-
- while (i < len - 1 && buf[i] == '\n')
- i++;
-
- for (; i < len; i = k) {
- for (k = i; k < len && buf[k] != '\n'; k++)
- ; /* do nothing */
- k++;
-
- if ((buf[k] == ' ' || buf[k] == '\t') && !first)
- continue;
-
- first = 0;
-
- for (j = 0; i + j < len; j++) {
- ch = buf[i + j];
- if (ch == ':')
- break;
- if (isalnum(ch) ||
- (ch == '-'))
- continue;
- return 0;
- }
- }
- return 1;
-}
-
-void append_signoff(struct strbuf *msgbuf, int ignore_footer)
+void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag)
{
+ unsigned no_dup_sob = flag & APPEND_SIGNOFF_DEDUP;
struct strbuf sob = STRBUF_INIT;
- int i;
+ int has_footer;
strbuf_addstr(&sob, sign_off_header);
strbuf_addstr(&sob, fmt_name(getenv("GIT_COMMITTER_NAME"),
getenv("GIT_COMMITTER_EMAIL")));
strbuf_addch(&sob, '\n');
- for (i = msgbuf->len - 1 - ignore_footer; i > 0 && msgbuf->buf[i - 1] != '\n'; i--)
- ; /* do nothing */
- if (prefixcmp(msgbuf->buf + i, sob.buf)) {
- if (!i || !ends_rfc2822_footer(msgbuf, ignore_footer))
- strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, "\n", 1);
- strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0, sob.buf, sob.len);
+
+ /*
+ * If the whole message buffer is equal to the sob, pretend that we
+ * found a conforming footer with a matching sob
+ */
+ if (msgbuf->len - ignore_footer == sob.len &&
+ !strncmp(msgbuf->buf, sob.buf, sob.len))
+ has_footer = 3;
+ else
+ has_footer = has_conforming_footer(msgbuf, &sob, ignore_footer);
+
+ if (!has_footer) {
+ const char *append_newlines = NULL;
+ size_t len = msgbuf->len - ignore_footer;
+
+ if (!len) {
+ /*
+ * The buffer is completely empty. Leave foom for
+ * the title and body to be filled in by the user.
+ */
+ append_newlines = "\n\n";
+ } else if (msgbuf->buf[len - 1] != '\n') {
+ /*
+ * Incomplete line. Complete the line and add a
+ * blank one so that there is an empty line between
+ * the message body and the sob.
+ */
+ append_newlines = "\n\n";
+ } else if (len == 1) {
+ /*
+ * Buffer contains a single newline. Add another
+ * so that we leave room for the title and body.
+ */
+ append_newlines = "\n";
+ } else if (msgbuf->buf[len - 2] != '\n') {
+ /*
+ * Buffer ends with a single newline. Add another
+ * so that there is an empty line between the message
+ * body and the sob.
+ */
+ append_newlines = "\n";
+ } /* else, the buffer already ends with two newlines. */
+
+ if (append_newlines)
+ strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
+ append_newlines, strlen(append_newlines));
}
+
+ if (has_footer != 3 && (!no_dup_sob || has_footer != 2))
+ strbuf_splice(msgbuf, msgbuf->len - ignore_footer, 0,
+ sob.buf, sob.len);
+
strbuf_release(&sob);
}
#define SEQ_TODO_FILE "sequencer/todo"
#define SEQ_OPTS_FILE "sequencer/opts"
+#define APPEND_SIGNOFF_DEDUP (1u << 0)
+
enum replay_action {
REPLAY_REVERT,
REPLAY_PICK
extern const char sign_off_header[];
-void append_signoff(struct strbuf *msgbuf, int ignore_footer);
+void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
#endif
*copyfrom && *copyfrom != ')';
copyfrom = nextat) {
size_t len = strcspn(copyfrom, ",)");
- if (copyfrom[len] == ')')
- nextat = copyfrom + len;
- else
+ if (copyfrom[len] == ',')
nextat = copyfrom + len + 1;
+ else
+ /* handle ')' and '\0' */
+ nextat = copyfrom + len;
if (!len)
continue;
for (i = 0; i < ARRAY_SIZE(pathspec_magic); i++)
die("Invalid pathspec magic '%.*s' in '%s'",
(int) len, copyfrom, elt);
}
- if (*copyfrom == ')')
- copyfrom++;
+ if (*copyfrom != ')')
+ die("Missing ')' at the end of pathspec magic in '%s'", elt);
+ copyfrom++;
} else {
/* shorthand */
for (copyfrom = elt + 1;
set_git_work_tree(core_worktree);
}
}
+ else if (!git_env_bool(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, 1)) {
+ /* #16d */
+ set_git_dir(gitdirenv);
+ free(gitfile);
+ return NULL;
+ }
else /* #2, #10 */
set_git_work_tree(".");
if (check_repository_format_gently(".", nongit_ok))
return NULL;
+ setenv(GIT_IMPLICIT_WORK_TREE_ENVIRONMENT, "0", 1);
+
/* --work-tree is set without --git-dir; use discovered one */
if (getenv(GIT_WORK_TREE_ENVIRONMENT) || git_work_tree_cfg) {
const char *gitdir;
prefix = setup_git_directory_gently_1(nongit_ok);
if (prefix)
- setenv("GIT_PREFIX", prefix, 1);
+ setenv(GIT_PREFIX_ENVIRONMENT, prefix, 1);
else
- setenv("GIT_PREFIX", "", 1);
+ setenv(GIT_PREFIX_ENVIRONMENT, "", 1);
if (startup_info) {
startup_info->have_repository = !nongit_ok || !*nongit_ok;
}
}
else if (mkdir(path, 0777)) {
- *pos = '/';
- return -1;
+ if (errno == EEXIST &&
+ !stat(path, &st) && S_ISDIR(st.st_mode)) {
+ ; /* somebody created it since we checked */
+ } else {
+ *pos = '/';
+ return -1;
+ }
}
else if (adjust_shared_perm(path)) {
*pos = '/';
}
struct grab_nth_branch_switch_cbdata {
- long cnt, alloc;
- struct strbuf *buf;
+ int remaining;
+ struct strbuf buf;
};
static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
struct grab_nth_branch_switch_cbdata *cb = cb_data;
const char *match = NULL, *target = NULL;
size_t len;
- int nth;
if (!prefixcmp(message, "checkout: moving from ")) {
match = message + strlen("checkout: moving from ");
if (!match || !target)
return 0;
-
- len = target - match;
- nth = cb->cnt++ % cb->alloc;
- strbuf_reset(&cb->buf[nth]);
- strbuf_add(&cb->buf[nth], match, len);
+ if (--(cb->remaining) == 0) {
+ len = target - match;
+ strbuf_reset(&cb->buf);
+ strbuf_add(&cb->buf, match, len);
+ return 1; /* we are done */
+ }
return 0;
}
static int interpret_nth_prior_checkout(const char *name, struct strbuf *buf)
{
long nth;
- int i, retval;
+ int retval;
struct grab_nth_branch_switch_cbdata cb;
const char *brace;
char *num_end;
brace = strchr(name, '}');
if (!brace)
return -1;
- nth = strtol(name+3, &num_end, 10);
+ nth = strtol(name + 3, &num_end, 10);
if (num_end != brace)
return -1;
if (nth <= 0)
return -1;
- cb.alloc = nth;
- cb.buf = xmalloc(nth * sizeof(struct strbuf));
- for (i = 0; i < nth; i++)
- strbuf_init(&cb.buf[i], 20);
- cb.cnt = 0;
+ cb.remaining = nth;
+ strbuf_init(&cb.buf, 20);
+
retval = 0;
- for_each_recent_reflog_ent("HEAD", grab_nth_branch_switch, 40960, &cb);
- if (cb.cnt < nth) {
- cb.cnt = 0;
- for_each_reflog_ent("HEAD", grab_nth_branch_switch, &cb);
+ if (0 < for_each_reflog_ent_reverse("HEAD", grab_nth_branch_switch, &cb)) {
+ strbuf_reset(buf);
+ strbuf_add(buf, cb.buf.buf, cb.buf.len);
+ retval = brace - name + 1;
}
- if (cb.cnt < nth)
- goto release_return;
- i = cb.cnt % nth;
- strbuf_reset(buf);
- strbuf_add(buf, cb.buf[i].buf, cb.buf[i].len);
- retval = brace-name+1;
-
-release_return:
- for (i = 0; i < nth; i++)
- strbuf_release(&cb.buf[i]);
- free(cb.buf);
+ strbuf_release(&cb.buf);
return retval;
}
+#include "cache.h"
#include "pkt-line.h"
#include "sideband.h"
while (1) {
int band, len;
- len = packet_read_line(in_stream, buf + pf, LARGE_PACKET_MAX);
+ len = packet_read(in_stream, NULL, NULL, buf + pf, LARGE_PACKET_MAX, 0);
if (len == 0)
break;
if (len < 1) {
} while (len);
continue;
case 1:
- safe_write(out, buf + pf+1, len);
+ write_or_die(out, buf + pf+1, len);
continue;
default:
fprintf(stderr, "%s: protocol error: bad band #%d\n",
if (0 <= band) {
sprintf(hdr, "%04x", n + 5);
hdr[4] = band;
- safe_write(fd, hdr, 5);
+ write_or_die(fd, hdr, 5);
} else {
sprintf(hdr, "%04x", n + 4);
- safe_write(fd, hdr, 4);
+ write_or_die(fd, hdr, 4);
}
- safe_write(fd, p, n);
+ write_or_die(fd, p, n);
p += n;
sz -= n;
}
#define SIDEBAND_PROTOCOL_ERROR -2
#define SIDEBAND_REMOTE_ERROR -1
-#define DEFAULT_PACKET_MAX 1000
-#define LARGE_PACKET_MAX 65520
-
int recv_sideband(const char *me, int in_stream, int out);
ssize_t send_sideband(int fd, int band, const char *data, ssize_t sz, int packet_max);
const char *del, const char *add, const char *reset)
{
struct rev_info rev;
- struct commit *left = left, *right = right;
+ struct commit *left = NULL, *right = NULL;
const char *message = NULL;
struct strbuf sb = STRBUF_INIT;
int fast_forward = 0, fast_backward = 0;
else if (!(left = lookup_commit_reference(one)) ||
!(right = lookup_commit_reference(two)))
message = "(commits not present)";
-
- if (!message &&
- prepare_submodule_summary(&rev, path, left, right,
- &fast_forward, &fast_backward))
+ else if (prepare_submodule_summary(&rev, path, left, right,
+ &fast_forward, &fast_backward))
message = "(revision walker failed)";
if (dirty_submodule & DIRTY_SUBMODULE_UNTRACKED)
strbuf_addf(&sb, "%s:%s\n", fast_backward ? " (rewind)" : "", reset);
fwrite(sb.buf, sb.len, 1, f);
- if (!message) {
+ if (!message) /* only NULL if we succeeded in setting up the walk */
print_submodule_summary(&rev, f, del, add, reset);
+ if (left)
clear_commit_marks(left, ~0);
+ if (right)
clear_commit_marks(right, ~0);
- }
strbuf_release(&sb);
}
test_description='respect crlf in git archive'
. ./test-lib.sh
-GIT_UNZIP=${GIT_UNZIP:-unzip}
-
-test_lazy_prereq UNZIP '
- "$GIT_UNZIP" -v
- test $? -ne 127
-'
test_expect_success setup '
grep " line 3 " error
'
+# good section hygiene
+test_expect_failure 'unsetting the last key in a section removes header' '
+ cat >.git/config <<-\EOF &&
+ # some generic comment on the configuration file itself
+ # a comment specific to this "section" section.
+ [section]
+ # some intervening lines
+ # that should also be dropped
+
+ key = value
+ # please be careful when you update the above variable
+ EOF
+
+ cat >expect <<-\EOF &&
+ # some generic comment on the configuration file itself
+ EOF
+
+ git config --unset section.key &&
+ test_cmp expect .git/config
+'
+
+test_expect_failure 'adding a key into an empty section reuses header' '
+ cat >.git/config <<-\EOF &&
+ [section]
+ EOF
+
+ q_to_tab >expect <<-\EOF &&
+ [section]
+ Qkey = value
+ EOF
+
+ git config section.key value
+ test_cmp expect .git/config
+'
+
test_done
"$here/16c/.git" "(null)" "$here/16c/sub" "(null)"
'
+test_expect_success '#16d: bareness preserved across alias' '
+ setup_repo 16d unset "" unset &&
+ (
+ cd 16d/.git &&
+ test_must_fail git status &&
+ git config alias.st status &&
+ test_must_fail git st
+ )
+'
+
+test_expect_success '#16e: bareness preserved by --bare' '
+ setup_repo 16e unset "" unset &&
+ (
+ cd 16e/.git &&
+ test_must_fail git status &&
+ test_must_fail git --bare status
+ )
+'
+
test_expect_success '#17: GIT_WORK_TREE without explicit GIT_DIR is accepted (bare case)' '
# Just like #16.
setup_repo 17a unset "" true &&
. ./test-lib.sh
-test_expect_success \
- 'setup' \
- 'mkdir path1 &&
- echo frotz >path0 &&
- echo rezrov >path1/file1 &&
- git update-index --add path0 path1/file1'
+test_expect_success 'setup' '
+ mkdir path1 &&
+ echo frotz >path0 &&
+ echo rezrov >path1/file1 &&
+ git update-index --add path0 path1/file1
+'
+
+test_expect_success SYMLINKS 'have symlink in place where dir is expected.' '
+ rm -fr path0 path1 &&
+ mkdir path2 &&
+ ln -s path2 path1 &&
+ git checkout-index -f -a &&
+ test ! -h path1 && test -d path1 &&
+ test -f path1/file1 && test ! -f path2/file1
+'
-test_expect_success SYMLINKS \
- 'have symlink in place where dir is expected.' \
- 'rm -fr path0 path1 &&
- mkdir path2 &&
- ln -s path2 path1 &&
- git checkout-index -f -a &&
- test ! -h path1 && test -d path1 &&
- test -f path1/file1 && test ! -f path2/file1'
+test_expect_success 'use --prefix=path2/' '
+ rm -fr path0 path1 path2 &&
+ mkdir path2 &&
+ git checkout-index --prefix=path2/ -f -a &&
+ test -f path2/path0 &&
+ test -f path2/path1/file1 &&
+ test ! -f path0 &&
+ test ! -f path1/file1
+'
+
+test_expect_success 'use --prefix=tmp-' '
+ rm -fr path0 path1 path2 tmp* &&
+ git checkout-index --prefix=tmp- -f -a &&
+ test -f tmp-path0 &&
+ test -f tmp-path1/file1 &&
+ test ! -f path0 &&
+ test ! -f path1/file1
+'
-test_expect_success \
- 'use --prefix=path2/' \
- 'rm -fr path0 path1 path2 &&
- mkdir path2 &&
- git checkout-index --prefix=path2/ -f -a &&
- test -f path2/path0 &&
- test -f path2/path1/file1 &&
- test ! -f path0 &&
- test ! -f path1/file1'
+test_expect_success 'use --prefix=tmp- but with a conflicting file and dir' '
+ rm -fr path0 path1 path2 tmp* &&
+ echo nitfol >tmp-path1 &&
+ mkdir tmp-path0 &&
+ git checkout-index --prefix=tmp- -f -a &&
+ test -f tmp-path0 &&
+ test -f tmp-path1/file1 &&
+ test ! -f path0 &&
+ test ! -f path1/file1
+'
-test_expect_success \
- 'use --prefix=tmp-' \
- 'rm -fr path0 path1 path2 tmp* &&
- git checkout-index --prefix=tmp- -f -a &&
- test -f tmp-path0 &&
- test -f tmp-path1/file1 &&
- test ! -f path0 &&
- test ! -f path1/file1'
+test_expect_success SYMLINKS 'use --prefix=tmp/orary/ where tmp is a symlink' '
+ rm -fr path0 path1 path2 tmp* &&
+ mkdir tmp1 tmp1/orary &&
+ ln -s tmp1 tmp &&
+ git checkout-index --prefix=tmp/orary/ -f -a &&
+ test -d tmp1/orary &&
+ test -f tmp1/orary/path0 &&
+ test -f tmp1/orary/path1/file1 &&
+ test -h tmp
+'
-test_expect_success \
- 'use --prefix=tmp- but with a conflicting file and dir' \
- 'rm -fr path0 path1 path2 tmp* &&
- echo nitfol >tmp-path1 &&
- mkdir tmp-path0 &&
- git checkout-index --prefix=tmp- -f -a &&
- test -f tmp-path0 &&
- test -f tmp-path1/file1 &&
- test ! -f path0 &&
- test ! -f path1/file1'
+test_expect_success SYMLINKS 'use --prefix=tmp/orary- where tmp is a symlink' '
+ rm -fr path0 path1 path2 tmp* &&
+ mkdir tmp1 &&
+ ln -s tmp1 tmp &&
+ git checkout-index --prefix=tmp/orary- -f -a &&
+ test -f tmp1/orary-path0 &&
+ test -f tmp1/orary-path1/file1 &&
+ test -h tmp
+'
-# Linus fix #1
-test_expect_success SYMLINKS \
- 'use --prefix=tmp/orary/ where tmp is a symlink' \
- 'rm -fr path0 path1 path2 tmp* &&
- mkdir tmp1 tmp1/orary &&
- ln -s tmp1 tmp &&
- git checkout-index --prefix=tmp/orary/ -f -a &&
- test -d tmp1/orary &&
- test -f tmp1/orary/path0 &&
- test -f tmp1/orary/path1/file1 &&
- test -h tmp'
+test_expect_success SYMLINKS 'use --prefix=tmp- where tmp-path1 is a symlink' '
+ rm -fr path0 path1 path2 tmp* &&
+ mkdir tmp1 &&
+ ln -s tmp1 tmp-path1 &&
+ git checkout-index --prefix=tmp- -f -a &&
+ test -f tmp-path0 &&
+ test ! -h tmp-path1 &&
+ test -d tmp-path1 &&
+ test -f tmp-path1/file1
+'
-# Linus fix #2
-test_expect_success SYMLINKS \
- 'use --prefix=tmp/orary- where tmp is a symlink' \
- 'rm -fr path0 path1 path2 tmp* &&
- mkdir tmp1 &&
- ln -s tmp1 tmp &&
- git checkout-index --prefix=tmp/orary- -f -a &&
- test -f tmp1/orary-path0 &&
- test -f tmp1/orary-path1/file1 &&
- test -h tmp'
+test_expect_success 'apply filter from working tree .gitattributes with --prefix' '
+ rm -fr path0 path1 path2 tmp* &&
+ mkdir path1 &&
+ mkdir tmp &&
+ git config filter.replace-all.smudge "sed -e s/./,/g" &&
+ git config filter.replace-all.clean cat &&
+ git config filter.replace-all.required true &&
+ echo "file1 filter=replace-all" >path1/.gitattributes &&
+ git checkout-index --prefix=tmp/ -f -a &&
+ echo frotz >expected &&
+ test_cmp expected tmp/path0 &&
+ echo ,,,,,, >expected &&
+ test_cmp expected tmp/path1/file1
+'
-# Linus fix #3
-test_expect_success SYMLINKS \
- 'use --prefix=tmp- where tmp-path1 is a symlink' \
- 'rm -fr path0 path1 path2 tmp* &&
- mkdir tmp1 &&
- ln -s tmp1 tmp-path1 &&
- git checkout-index --prefix=tmp- -f -a &&
- test -f tmp-path0 &&
- test ! -h tmp-path1 &&
- test -d tmp-path1 &&
- test -f tmp-path1/file1'
+test_expect_success 'apply CRLF filter from working tree .gitattributes with --prefix' '
+ rm -fr path0 path1 path2 tmp* &&
+ mkdir path1 &&
+ mkdir tmp &&
+ echo "file1 eol=crlf" >path1/.gitattributes &&
+ git checkout-index --prefix=tmp/ -f -a &&
+ echo rezrovQ >expected &&
+ tr \\015 Q <tmp/path1/file1 >actual &&
+ test_cmp expected actual
+'
test_done
. ./test-lib.sh
-test_expect_success \
- 'prepare a trivial repository' \
- 'echo Hello > A &&
- git update-index --add A &&
- git commit -m "Initial commit." &&
- echo World >> A &&
- git update-index --add A &&
- git commit -m "Second commit." &&
- HEAD=$(git rev-parse --verify HEAD)'
-
-test_expect_success \
- 'git branch --help should not have created a bogus branch' '
- test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
- test_path_is_missing .git/refs/heads/--help
+test_expect_success 'prepare a trivial repository' '
+ echo Hello >A &&
+ git update-index --add A &&
+ git commit -m "Initial commit." &&
+ echo World >>A &&
+ git update-index --add A &&
+ git commit -m "Second commit." &&
+ HEAD=$(git rev-parse --verify HEAD)'
+
+test_expect_success 'git branch --help should not have created a bogus branch' '
+ test_might_fail git branch --help </dev/null >/dev/null 2>/dev/null &&
+ test_path_is_missing .git/refs/heads/--help
'
test_expect_success 'branch -h in broken repository' '
test_i18ngrep "[Uu]sage" broken/usage
'
-test_expect_success \
- 'git branch abc should create a branch' \
- 'git branch abc && test_path_is_file .git/refs/heads/abc'
+test_expect_success 'git branch abc should create a branch' '
+ git branch abc && test_path_is_file .git/refs/heads/abc
+'
-test_expect_success \
- 'git branch a/b/c should create a branch' \
- 'git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c'
+test_expect_success 'git branch a/b/c should create a branch' '
+ git branch a/b/c && test_path_is_file .git/refs/heads/a/b/c
+'
-test_expect_success \
- 'git branch HEAD should fail' \
- 'test_must_fail git branch HEAD'
+test_expect_success 'git branch HEAD should fail' '
+ test_must_fail git branch HEAD
+'
cat >expect <<EOF
$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
-test_expect_success \
- 'git branch -l d/e/f should create a branch and a log' \
- 'GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git branch -l d/e/f &&
- test_path_is_file .git/refs/heads/d/e/f &&
- test_path_is_file .git/logs/refs/heads/d/e/f &&
- test_cmp expect .git/logs/refs/heads/d/e/f'
-
-test_expect_success \
- 'git branch -d d/e/f should delete a branch and a log' \
- 'git branch -d d/e/f &&
- test_path_is_missing .git/refs/heads/d/e/f &&
- test_path_is_missing .git/logs/refs/heads/d/e/f'
-
-test_expect_success \
- 'git branch j/k should work after branch j has been deleted' \
- 'git branch j &&
- git branch -d j &&
- git branch j/k'
-
-test_expect_success \
- 'git branch l should work after branch l/m has been deleted' \
- 'git branch l/m &&
- git branch -d l/m &&
- git branch l'
-
-test_expect_success \
- 'git branch -m dumps usage' \
- 'test_expect_code 128 git branch -m 2>err &&
- test_i18ngrep "too many branches for a rename operation" err'
-
-test_expect_success \
- 'git branch -m m m/m should work' \
- 'git branch -l m &&
- git branch -m m m/m &&
- test_path_is_file .git/logs/refs/heads/m/m'
-
-test_expect_success \
- 'git branch -m n/n n should work' \
- 'git branch -l n/n &&
+test_expect_success 'git branch -l d/e/f should create a branch and a log' '
+ GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git branch -l d/e/f &&
+ test_path_is_file .git/refs/heads/d/e/f &&
+ test_path_is_file .git/logs/refs/heads/d/e/f &&
+ test_cmp expect .git/logs/refs/heads/d/e/f
+'
+
+test_expect_success 'git branch -d d/e/f should delete a branch and a log' '
+ git branch -d d/e/f &&
+ test_path_is_missing .git/refs/heads/d/e/f &&
+ test_path_is_missing .git/logs/refs/heads/d/e/f
+'
+
+test_expect_success 'git branch j/k should work after branch j has been deleted' '
+ git branch j &&
+ git branch -d j &&
+ git branch j/k
+'
+
+test_expect_success 'git branch l should work after branch l/m has been deleted' '
+ git branch l/m &&
+ git branch -d l/m &&
+ git branch l
+'
+
+test_expect_success 'git branch -m dumps usage' '
+ test_expect_code 128 git branch -m 2>err &&
+ test_i18ngrep "too many branches for a rename operation" err
+'
+
+test_expect_success 'git branch -m m m/m should work' '
+ git branch -l m &&
+ git branch -m m m/m &&
+ test_path_is_file .git/logs/refs/heads/m/m
+'
+
+test_expect_success 'git branch -m n/n n should work' '
+ git branch -l n/n &&
git branch -m n/n n &&
- test_path_is_file .git/logs/refs/heads/n'
+ test_path_is_file .git/logs/refs/heads/n
+'
test_expect_success 'git branch -m o/o o should fail when o/p exists' '
git branch o/o &&
- git branch o/p &&
+ git branch o/p &&
test_must_fail git branch -m o/o o
'
git config branch.s/s.dummy Hello
-test_expect_success \
- 'git branch -m s/s s should work when s/t is deleted' \
- 'git branch -l s/s &&
+test_expect_success 'git branch -m s/s s should work when s/t is deleted' '
+ git branch -l s/s &&
test_path_is_file .git/logs/refs/heads/s/s &&
- git branch -l s/t &&
+ git branch -l s/t &&
test_path_is_file .git/logs/refs/heads/s/t &&
- git branch -d s/t &&
- git branch -m s/s s &&
- test_path_is_file .git/logs/refs/heads/s'
+ git branch -d s/t &&
+ git branch -m s/s s &&
+ test_path_is_file .git/logs/refs/heads/s
+'
-test_expect_success 'config information was renamed, too' \
- "test $(git config branch.s.dummy) = Hello &&
- test_must_fail git config branch.s/s/dummy"
+test_expect_success 'config information was renamed, too' '
+ test $(git config branch.s.dummy) = Hello &&
+ test_must_fail git config branch.s/s/dummy
+'
test_expect_success 'deleting a symref' '
git branch target &&
test_i18ncmp expect actual
'
-test_expect_success 'renaming a symref is not allowed' \
-'
+test_expect_success 'renaming a symref is not allowed' '
git symbolic-ref refs/heads/master2 refs/heads/master &&
test_must_fail git branch -m master2 master3 &&
git symbolic-ref refs/heads/master2 &&
test_path_is_missing .git/refs/heads/master3
'
-test_expect_success SYMLINKS \
- 'git branch -m u v should fail when the reflog for u is a symlink' '
- git branch -l u &&
- mv .git/logs/refs/heads/u real-u &&
- ln -s real-u .git/logs/refs/heads/u &&
- test_must_fail git branch -m u v
-'
-
-test_expect_success 'test tracking setup via --track' \
- 'git config remote.local.url . &&
- git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch --track my1 local/master &&
- test $(git config branch.my1.remote) = local &&
- test $(git config branch.my1.merge) = refs/heads/master'
-
-test_expect_success 'test tracking setup (non-wildcard, matching)' \
- 'git config remote.local.url . &&
- git config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
- (git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch --track my4 local/master &&
- test $(git config branch.my4.remote) = local &&
- test $(git config branch.my4.merge) = refs/heads/master'
-
-test_expect_success 'test tracking setup (non-wildcard, not matching)' \
- 'git config remote.local.url . &&
- git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
- (git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch --track my5 local/master &&
- ! test "$(git config branch.my5.remote)" = local &&
- ! test "$(git config branch.my5.merge)" = refs/heads/master'
-
-test_expect_success 'test tracking setup via config' \
- 'git config branch.autosetupmerge true &&
- git config remote.local.url . &&
- git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch my3 local/master &&
- test $(git config branch.my3.remote) = local &&
- test $(git config branch.my3.merge) = refs/heads/master'
-
-test_expect_success 'test overriding tracking setup via --no-track' \
- 'git config branch.autosetupmerge true &&
- git config remote.local.url . &&
- git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git show-ref -q refs/remotes/local/master || git fetch local) &&
- git branch --no-track my2 local/master &&
- git config branch.autosetupmerge false &&
- ! test "$(git config branch.my2.remote)" = local &&
- ! test "$(git config branch.my2.merge)" = refs/heads/master'
-
-test_expect_success 'no tracking without .fetch entries' \
- 'git config branch.autosetupmerge true &&
- git branch my6 s &&
- git config branch.automsetupmerge false &&
- test -z "$(git config branch.my6.remote)" &&
- test -z "$(git config branch.my6.merge)"'
-
-test_expect_success 'test tracking setup via --track but deeper' \
- 'git config remote.local.url . &&
- git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
- (git show-ref -q refs/remotes/local/o/o || git fetch local) &&
- git branch --track my7 local/o/o &&
- test "$(git config branch.my7.remote)" = local &&
- test "$(git config branch.my7.merge)" = refs/heads/o/o'
-
-test_expect_success 'test deleting branch deletes branch config' \
- 'git branch -d my7 &&
- test -z "$(git config branch.my7.remote)" &&
- test -z "$(git config branch.my7.merge)"'
-
-test_expect_success 'test deleting branch without config' \
- 'git branch my7 s &&
- sha1=$(git rev-parse my7 | cut -c 1-7) &&
- echo "Deleted branch my7 (was $sha1)." >expect &&
- git branch -d my7 >actual 2>&1 &&
- test_i18ncmp expect actual'
-
-test_expect_success 'test --track without .fetch entries' \
- 'git branch --track my8 &&
- test "$(git config branch.my8.remote)" &&
- test "$(git config branch.my8.merge)"'
-
-test_expect_success \
- 'branch from non-branch HEAD w/autosetupmerge=always' \
- 'git config branch.autosetupmerge always &&
- git branch my9 HEAD^ &&
- git config branch.autosetupmerge false'
-
-test_expect_success \
- 'branch from non-branch HEAD w/--track causes failure' \
- 'test_must_fail git branch --track my10 HEAD^'
-
-test_expect_success \
- 'branch from tag w/--track causes failure' \
- 'git tag foobar &&
- test_must_fail git branch --track my11 foobar'
-
-test_expect_success '--set-upstream-to fails on multiple branches' \
- 'test_must_fail git branch --set-upstream-to master a b c'
-
-test_expect_success '--set-upstream-to fails on detached HEAD' \
- 'git checkout HEAD^{} &&
- test_must_fail git branch --set-upstream-to master &&
- git checkout -'
-
-test_expect_success 'use --set-upstream-to modify HEAD' \
- 'test_config branch.master.remote foo &&
- test_config branch.master.merge foo &&
- git branch my12
- git branch --set-upstream-to my12 &&
- test "$(git config branch.master.remote)" = "." &&
- test "$(git config branch.master.merge)" = "refs/heads/my12"'
-
-test_expect_success 'use --set-upstream-to modify a particular branch' \
- 'git branch my13
- git branch --set-upstream-to master my13 &&
- test "$(git config branch.my13.remote)" = "." &&
- test "$(git config branch.my13.merge)" = "refs/heads/master"'
-
-test_expect_success '--unset-upstream should fail if given a non-existent branch' \
- 'test_must_fail git branch --unset-upstream i-dont-exist'
-
-test_expect_success 'test --unset-upstream on HEAD' \
- 'git branch my14
- test_config branch.master.remote foo &&
- test_config branch.master.merge foo &&
- git branch --set-upstream-to my14 &&
- git branch --unset-upstream &&
- test_must_fail git config branch.master.remote &&
- test_must_fail git config branch.master.merge &&
- # fail for a branch without upstream set
- test_must_fail git branch --unset-upstream
-'
-
-test_expect_success '--unset-upstream should fail on multiple branches' \
- 'test_must_fail git branch --unset-upstream a b c'
-
-test_expect_success '--unset-upstream should fail on detached HEAD' \
- 'git checkout HEAD^{} &&
- test_must_fail git branch --unset-upstream &&
- git checkout -
-'
-
-test_expect_success 'test --unset-upstream on a particular branch' \
- 'git branch my15
- git branch --set-upstream-to master my14 &&
- git branch --unset-upstream my14 &&
- test_must_fail git config branch.my14.remote &&
- test_must_fail git config branch.my14.merge'
-
-test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' \
- 'git update-ref refs/remotes/origin/master HEAD &&
- git branch --set-upstream origin/master 2>actual &&
- test_when_finished git update-ref -d refs/remotes/origin/master &&
- test_when_finished git branch -d origin/master &&
- cat >expected <<EOF &&
+test_expect_success SYMLINKS 'git branch -m u v should fail when the reflog for u is a symlink' '
+ git branch -l u &&
+ mv .git/logs/refs/heads/u real-u &&
+ ln -s real-u .git/logs/refs/heads/u &&
+ test_must_fail git branch -m u v
+'
+
+test_expect_success 'test tracking setup via --track' '
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/master || git fetch local) &&
+ git branch --track my1 local/master &&
+ test $(git config branch.my1.remote) = local &&
+ test $(git config branch.my1.merge) = refs/heads/master
+'
+
+test_expect_success 'test tracking setup (non-wildcard, matching)' '
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/master:refs/remotes/local/master &&
+ (git show-ref -q refs/remotes/local/master || git fetch local) &&
+ git branch --track my4 local/master &&
+ test $(git config branch.my4.remote) = local &&
+ test $(git config branch.my4.merge) = refs/heads/master
+'
+
+test_expect_success 'test tracking setup (non-wildcard, not matching)' '
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/s:refs/remotes/local/s &&
+ (git show-ref -q refs/remotes/local/master || git fetch local) &&
+ git branch --track my5 local/master &&
+ ! test "$(git config branch.my5.remote)" = local &&
+ ! test "$(git config branch.my5.merge)" = refs/heads/master
+'
+
+test_expect_success 'test tracking setup via config' '
+ git config branch.autosetupmerge true &&
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/master || git fetch local) &&
+ git branch my3 local/master &&
+ test $(git config branch.my3.remote) = local &&
+ test $(git config branch.my3.merge) = refs/heads/master
+'
+
+test_expect_success 'test overriding tracking setup via --no-track' '
+ git config branch.autosetupmerge true &&
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/master || git fetch local) &&
+ git branch --no-track my2 local/master &&
+ git config branch.autosetupmerge false &&
+ ! test "$(git config branch.my2.remote)" = local &&
+ ! test "$(git config branch.my2.merge)" = refs/heads/master
+'
+
+test_expect_success 'no tracking without .fetch entries' '
+ git config branch.autosetupmerge true &&
+ git branch my6 s &&
+ git config branch.automsetupmerge false &&
+ test -z "$(git config branch.my6.remote)" &&
+ test -z "$(git config branch.my6.merge)"
+'
+
+test_expect_success 'test tracking setup via --track but deeper' '
+ git config remote.local.url . &&
+ git config remote.local.fetch refs/heads/*:refs/remotes/local/* &&
+ (git show-ref -q refs/remotes/local/o/o || git fetch local) &&
+ git branch --track my7 local/o/o &&
+ test "$(git config branch.my7.remote)" = local &&
+ test "$(git config branch.my7.merge)" = refs/heads/o/o
+'
+
+test_expect_success 'test deleting branch deletes branch config' '
+ git branch -d my7 &&
+ test -z "$(git config branch.my7.remote)" &&
+ test -z "$(git config branch.my7.merge)"
+'
+
+test_expect_success 'test deleting branch without config' '
+ git branch my7 s &&
+ sha1=$(git rev-parse my7 | cut -c 1-7) &&
+ echo "Deleted branch my7 (was $sha1)." >expect &&
+ git branch -d my7 >actual 2>&1 &&
+ test_i18ncmp expect actual
+'
+
+test_expect_success 'test --track without .fetch entries' '
+ git branch --track my8 &&
+ test "$(git config branch.my8.remote)" &&
+ test "$(git config branch.my8.merge)"
+'
+
+test_expect_success 'branch from non-branch HEAD w/autosetupmerge=always' '
+ git config branch.autosetupmerge always &&
+ git branch my9 HEAD^ &&
+ git config branch.autosetupmerge false
+'
+
+test_expect_success 'branch from non-branch HEAD w/--track causes failure' '
+ test_must_fail git branch --track my10 HEAD^
+'
+
+test_expect_success 'branch from tag w/--track causes failure' '
+ git tag foobar &&
+ test_must_fail git branch --track my11 foobar
+'
+
+test_expect_success '--set-upstream-to fails on multiple branches' '
+ test_must_fail git branch --set-upstream-to master a b c
+'
+
+test_expect_success '--set-upstream-to fails on detached HEAD' '
+ git checkout HEAD^{} &&
+ test_must_fail git branch --set-upstream-to master &&
+ git checkout -
+'
+
+test_expect_success 'use --set-upstream-to modify HEAD' '
+ test_config branch.master.remote foo &&
+ test_config branch.master.merge foo &&
+ git branch my12
+ git branch --set-upstream-to my12 &&
+ test "$(git config branch.master.remote)" = "." &&
+ test "$(git config branch.master.merge)" = "refs/heads/my12"
+'
+
+test_expect_success 'use --set-upstream-to modify a particular branch' '
+ git branch my13
+ git branch --set-upstream-to master my13 &&
+ test "$(git config branch.my13.remote)" = "." &&
+ test "$(git config branch.my13.merge)" = "refs/heads/master"
+'
+
+test_expect_success '--unset-upstream should fail if given a non-existent branch' '
+ test_must_fail git branch --unset-upstream i-dont-exist
+'
+
+test_expect_success 'test --unset-upstream on HEAD' '
+ git branch my14
+ test_config branch.master.remote foo &&
+ test_config branch.master.merge foo &&
+ git branch --set-upstream-to my14 &&
+ git branch --unset-upstream &&
+ test_must_fail git config branch.master.remote &&
+ test_must_fail git config branch.master.merge &&
+ # fail for a branch without upstream set
+ test_must_fail git branch --unset-upstream
+'
+
+test_expect_success '--unset-upstream should fail on multiple branches' '
+ test_must_fail git branch --unset-upstream a b c
+'
+
+test_expect_success '--unset-upstream should fail on detached HEAD' '
+ git checkout HEAD^{} &&
+ test_must_fail git branch --unset-upstream &&
+ git checkout -
+'
+
+test_expect_success 'test --unset-upstream on a particular branch' '
+ git branch my15
+ git branch --set-upstream-to master my14 &&
+ git branch --unset-upstream my14 &&
+ test_must_fail git config branch.my14.remote &&
+ test_must_fail git config branch.my14.merge
+'
+
+test_expect_success '--set-upstream shows message when creating a new branch that exists as remote-tracking' '
+ git update-ref refs/remotes/origin/master HEAD &&
+ git branch --set-upstream origin/master 2>actual &&
+ test_when_finished git update-ref -d refs/remotes/origin/master &&
+ test_when_finished git branch -d origin/master &&
+ cat >expected <<EOF &&
The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
If you wanted to make '"'master'"' track '"'origin/master'"', do this:
git branch -d origin/master
git branch --set-upstream-to origin/master
EOF
- test_cmp expected actual
+ test_cmp expected actual
'
-test_expect_success '--set-upstream with two args only shows the deprecation message' \
- 'git branch --set-upstream master my13 2>actual &&
- test_when_finished git branch --unset-upstream master &&
- cat >expected <<EOF &&
+test_expect_success '--set-upstream with two args only shows the deprecation message' '
+ git branch --set-upstream master my13 2>actual &&
+ test_when_finished git branch --unset-upstream master &&
+ cat >expected <<EOF &&
The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
EOF
- test_cmp expected actual
+ test_cmp expected actual
'
-test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' \
- 'git branch --set-upstream my13 2>actual &&
- test_when_finished git branch --unset-upstream my13 &&
- cat >expected <<EOF &&
+test_expect_success '--set-upstream with one arg only shows the deprecation message if the branch existed' '
+ git branch --set-upstream my13 2>actual &&
+ test_when_finished git branch --unset-upstream my13 &&
+ cat >expected <<EOF &&
The --set-upstream flag is deprecated and will be removed. Consider using --track or --set-upstream-to
EOF
- test_cmp expected actual
+ test_cmp expected actual
'
# Keep this test last, as it changes the current branch
cat >expect <<EOF
$_z40 $HEAD $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000 branch: Created from master
EOF
-test_expect_success \
- 'git checkout -b g/h/i -l should create a branch and a log' \
- 'GIT_COMMITTER_DATE="2005-05-26 23:30" \
- git checkout -b g/h/i -l master &&
- test_path_is_file .git/refs/heads/g/h/i &&
- test_path_is_file .git/logs/refs/heads/g/h/i &&
- test_cmp expect .git/logs/refs/heads/g/h/i'
+test_expect_success 'git checkout -b g/h/i -l should create a branch and a log' '
+ GIT_COMMITTER_DATE="2005-05-26 23:30" \
+ git checkout -b g/h/i -l master &&
+ test_path_is_file .git/refs/heads/g/h/i &&
+ test_path_is_file .git/logs/refs/heads/g/h/i &&
+ test_cmp expect .git/logs/refs/heads/g/h/i
+'
test_expect_success 'checkout -b makes reflog by default' '
git checkout master &&
test_expect_success 'detect misconfigured autosetuprebase (no value)' '
git config --unset branch.autosetuprebase &&
- echo "[branch] autosetuprebase" >> .git/config &&
+ echo "[branch] autosetuprebase" >>.git/config &&
test_must_fail git branch &&
git config --unset branch.autosetuprebase
'
test_must_fail git branch -v branch*
'
-cat >expect <<'EOF'
-* (no branch)
+test_expect_success 'git branch shows detached HEAD properly' '
+ cat >expect <<EOF &&
+* (detached from $(git rev-parse --short HEAD^0))
branch-one
branch-two
master
EOF
-test_expect_success 'git branch shows detached HEAD properly' '
git checkout HEAD^0 &&
git branch >actual &&
test_i18ncmp expect actual
--- /dev/null
+#!/bin/sh
+
+test_description='tests for the peel_ref optimization of packed-refs'
+. ./test-lib.sh
+
+test_expect_success 'create annotated tag in refs/tags' '
+ test_commit base &&
+ git tag -m annotated foo
+'
+
+test_expect_success 'create annotated tag outside of refs/tags' '
+ git update-ref refs/outside/foo refs/tags/foo
+'
+
+# This matches show-ref's output
+print_ref() {
+ echo "$(git rev-parse "$1") $1"
+}
+
+test_expect_success 'set up expected show-ref output' '
+ {
+ print_ref "refs/heads/master" &&
+ print_ref "refs/outside/foo" &&
+ print_ref "refs/outside/foo^{}" &&
+ print_ref "refs/tags/base" &&
+ print_ref "refs/tags/foo" &&
+ print_ref "refs/tags/foo^{}"
+ } >expect
+'
+
+test_expect_success 'refs are peeled outside of refs/tags (loose)' '
+ git show-ref -d >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'refs are peeled outside of refs/tags (packed)' '
+ git pack-refs --all &&
+ git show-ref -d >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'create old-style pack-refs without fully-peeled' '
+ # Git no longer writes without fully-peeled, so we just write our own
+ # from scratch; we could also munge the existing file to remove the
+ # fully-peeled bits, but that seems even more prone to failure,
+ # especially if the format ever changes again. At least this way we
+ # know we are emulating exactly what an older git would have written.
+ {
+ echo "# pack-refs with: peeled " &&
+ print_ref "refs/heads/master" &&
+ print_ref "refs/outside/foo" &&
+ print_ref "refs/tags/base" &&
+ print_ref "refs/tags/foo" &&
+ echo "^$(git rev-parse "refs/tags/foo^{}")"
+ } >tmp &&
+ mv tmp .git/packed-refs
+'
+
+test_expect_success 'refs are peeled outside of refs/tags (old packed)' '
+ git show-ref -d >actual &&
+ test_cmp expect actual
+'
+
+test_done
'
test_expect_success 'rebase is not broken by diff.renames' '
- git config diff.renames copies &&
- test_when_finished "git config --unset diff.renames" &&
+ test_config diff.renames copies &&
git checkout filemove &&
GIT_TRACE=1 git rebase force-3way
'
test_expect_success 'rebase -i respects core.commentchar' '
git reset --hard &&
git checkout E^0 &&
- git config core.commentchar "\\" &&
- test_when_finished "git config --unset core.commentchar" &&
+ test_config core.commentchar "\\" &&
write_script remove-all-but-first.sh <<-\EOF &&
sed -e "2,\$s/^/\\\\/" "$1" >"$1.tmp" &&
mv "$1.tmp" "$1"
--- /dev/null
+#!/bin/sh
+
+test_description='Test cherry-pick -x and -s'
+
+. ./test-lib.sh
+
+pristine_detach () {
+ git cherry-pick --quit &&
+ git checkout -f "$1^0" &&
+ git read-tree -u --reset HEAD &&
+ git clean -d -f -f -q -x
+}
+
+mesg_one_line='base: commit message'
+
+mesg_no_footer="$mesg_one_line
+
+OneWordBodyThatsNotA-S-o-B"
+
+mesg_with_footer="$mesg_no_footer
+
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+Signed-off-by: A.U. Thor <author@example.com>
+Signed-off-by: B.U. Thor <buthor@example.com>"
+
+mesg_broken_footer="$mesg_no_footer
+
+The signed-off-by string should begin with the words Signed-off-by followed
+by a colon and space, and then the signers name and email address. e.g.
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+
+mesg_with_footer_sob="$mesg_with_footer
+Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>"
+
+mesg_with_cherry_footer="$mesg_with_footer_sob
+(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+Tested-by: C.U. Thor <cuthor@example.com>"
+
+
+test_expect_success setup '
+ git config advice.detachedhead false &&
+ echo unrelated >unrelated &&
+ git add unrelated &&
+ test_commit initial foo a &&
+ test_commit "$mesg_one_line" foo b mesg-one-line &&
+ git reset --hard initial &&
+ test_commit "$mesg_no_footer" foo b mesg-no-footer &&
+ git reset --hard initial &&
+ test_commit "$mesg_broken_footer" foo b mesg-broken-footer &&
+ git reset --hard initial &&
+ test_commit "$mesg_with_footer" foo b mesg-with-footer &&
+ git reset --hard initial &&
+ test_commit "$mesg_with_footer_sob" foo b mesg-with-footer-sob &&
+ git reset --hard initial &&
+ test_commit "$mesg_with_cherry_footer" foo b mesg-with-cherry-footer &&
+ pristine_detach initial &&
+ test_commit conflicting unrelated
+'
+
+test_expect_success 'cherry-pick -x inserts blank line after one line subject' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-one-line^0` &&
+ git cherry-pick -x mesg-one-line &&
+ cat <<-EOF >expect &&
+ $mesg_one_line
+
+ (cherry picked from commit $sha1)
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s inserts blank line after one line subject' '
+ pristine_detach initial &&
+ git cherry-pick -s mesg-one-line &&
+ cat <<-EOF >expect &&
+ $mesg_one_line
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s inserts blank line after non-conforming footer' '
+ pristine_detach initial &&
+ git cherry-pick -s mesg-broken-footer &&
+ cat <<-EOF >expect &&
+ $mesg_broken_footer
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x inserts blank line when conforming footer not found' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-no-footer^0` &&
+ git cherry-pick -x mesg-no-footer &&
+ cat <<-EOF >expect &&
+ $mesg_no_footer
+
+ (cherry picked from commit $sha1)
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s inserts blank line when conforming footer not found' '
+ pristine_detach initial &&
+ git cherry-pick -s mesg-no-footer &&
+ cat <<-EOF >expect &&
+ $mesg_no_footer
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s inserts blank line when conforming footer not found' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-no-footer^0` &&
+ git cherry-pick -x -s mesg-no-footer &&
+ cat <<-EOF >expect &&
+ $mesg_no_footer
+
+ (cherry picked from commit $sha1)
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s adds sob when last sob doesnt match committer' '
+ pristine_detach initial &&
+ git cherry-pick -s mesg-with-footer &&
+ cat <<-EOF >expect &&
+ $mesg_with_footer
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s adds sob when last sob doesnt match committer' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-with-footer^0` &&
+ git cherry-pick -x -s mesg-with-footer &&
+ cat <<-EOF >expect &&
+ $mesg_with_footer
+ (cherry picked from commit $sha1)
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s refrains from adding duplicate trailing sob' '
+ pristine_detach initial &&
+ git cherry-pick -s mesg-with-footer-sob &&
+ cat <<-EOF >expect &&
+ $mesg_with_footer_sob
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s adds sob even when trailing sob exists for committer' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-with-footer-sob^0` &&
+ git cherry-pick -x -s mesg-with-footer-sob &&
+ cat <<-EOF >expect &&
+ $mesg_with_footer_sob
+ (cherry picked from commit $sha1)
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x treats "(cherry picked from..." line as part of footer' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-with-cherry-footer^0` &&
+ git cherry-pick -x mesg-with-cherry-footer &&
+ cat <<-EOF >expect &&
+ $mesg_with_cherry_footer
+ (cherry picked from commit $sha1)
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -s treats "(cherry picked from..." line as part of footer' '
+ pristine_detach initial &&
+ git cherry-pick -s mesg-with-cherry-footer &&
+ cat <<-EOF >expect &&
+ $mesg_with_cherry_footer
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'cherry-pick -x -s treats "(cherry picked from..." line as part of footer' '
+ pristine_detach initial &&
+ sha1=`git rev-parse mesg-with-cherry-footer^0` &&
+ git cherry-pick -x -s mesg-with-cherry-footer &&
+ cat <<-EOF >expect &&
+ $mesg_with_cherry_footer
+ (cherry picked from commit $sha1)
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+ EOF
+ git log -1 --pretty=format:%B >actual &&
+ test_cmp expect actual
+'
+
+test_done
grep warning actual.err
'
+test_expect_success 'rename pretty print with nothing in common' '
+ mkdir -p a/b/ &&
+ : >a/b/c &&
+ git add a/b/c &&
+ git commit -m "create a/b/c" &&
+ mkdir -p c/b/ &&
+ git mv a/b/c c/b/a &&
+ git commit -m "a/b/c -> c/b/a" &&
+ git diff -M --summary HEAD^ HEAD >output &&
+ test_i18ngrep " a/b/c => c/b/a " output &&
+ git diff -M --stat HEAD^ HEAD >output &&
+ test_i18ngrep " a/b/c => c/b/a " output
+'
+
+test_expect_success 'rename pretty print with common prefix' '
+ mkdir -p c/d &&
+ git mv c/b/a c/d/e &&
+ git commit -m "c/b/a -> c/d/e" &&
+ git diff -M --summary HEAD^ HEAD >output &&
+ test_i18ngrep " c/{b/a => d/e} " output &&
+ git diff -M --stat HEAD^ HEAD >output &&
+ test_i18ngrep " c/{b/a => d/e} " output
+'
+
+test_expect_success 'rename pretty print with common suffix' '
+ mkdir d &&
+ git mv c/d/e d/e &&
+ git commit -m "c/d/e -> d/e" &&
+ git diff -M --summary HEAD^ HEAD >output &&
+ test_i18ngrep " {c/d => d}/e " output &&
+ git diff -M --stat HEAD^ HEAD >output &&
+ test_i18ngrep " {c/d => d}/e " output
+'
+
+test_expect_success 'rename pretty print with common prefix and suffix' '
+ mkdir d/f &&
+ git mv d/e d/f/e &&
+ git commit -m "d/e -> d/f/e" &&
+ git diff -M --summary HEAD^ HEAD >output &&
+ test_i18ngrep " d/{ => f}/e " output &&
+ git diff -M --stat HEAD^ HEAD >output &&
+ test_i18ngrep " d/{ => f}/e " output
+'
+
+test_expect_success 'rename pretty print common prefix and suffix overlap' '
+ mkdir d/f/f &&
+ git mv d/f/e d/f/f/e &&
+ git commit -m "d/f/e d/f/f/e" &&
+ git diff -M --summary HEAD^ HEAD >output &&
+ test_i18ngrep " d/f/{ => f}/e " output &&
+ git diff -M --stat HEAD^ HEAD >output &&
+ test_i18ngrep " d/f/{ => f}/e " output
+'
+
test_done
=?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
=?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
=?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
- =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?=
- =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
- =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?=
- =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
=?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
=?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
=?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
- =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?=
- =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
- =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?=
- =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
=?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
=?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
=?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
- =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3?=
- =?UTF-8?q?=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
- =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3?=
- =?UTF-8?q?=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
- =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+ =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
+ =?UTF-8?q?bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6?=
+ =?UTF-8?q?=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6?=
+ =?UTF-8?q?=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f?=
+ =?UTF-8?q?=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar?=
+ =?UTF-8?q?=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20bar=20f=C3=B6=C3=B6=20?=
+ =?UTF-8?q?bar?=
EOF
test_expect_success 'format-patch wraps extremely long subject (rfc2047)' '
rm -rf patches/ &&
test_cmp expect actual
'
+append_signoff()
+{
+ C=$(git commit-tree HEAD^^{tree} -p HEAD) &&
+ git format-patch --stdout --signoff $C^..$C >append_signoff.patch &&
+ sed -n -e "1,/^---$/p" append_signoff.patch |
+ egrep -n "^Subject|Sign|^$"
+}
+
+test_expect_success 'signoff: commit with no body' '
+ append_signoff </dev/null >actual &&
+ cat <<\EOF | sed "s/EOL$//" >expected &&
+4:Subject: [PATCH] EOL
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: commit with only subject' '
+ echo subject | append_signoff >actual &&
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: commit with only subject that does not end with NL' '
+ printf subject | append_signoff >actual &&
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: no existing signoffs' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: no existing signoffs and no trailing NL' '
+ printf "subject\n\nbody" | append_signoff >actual &&
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: some random signoff' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: my@house
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: my@house
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: misc conforming footer elements' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: my@house
+(cherry picked from commit da39a3ee5e6b4b0d3255bfef95601890afd80709)
+Tested-by: Some One <someone@example.com>
+Bug: 1234
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: my@house
+15:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: some random signoff-alike' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+Fooled-by-me: my@house
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+11:
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: not really a signoff' '
+ append_signoff <<\EOF >actual &&
+subject
+
+I want to mention about Signed-off-by: here.
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:I want to mention about Signed-off-by: here.
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: not really a signoff (2)' '
+ append_signoff <<\EOF >actual &&
+subject
+
+My unfortunate
+Signed-off-by: example happens to be wrapped here.
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:Signed-off-by: example happens to be wrapped here.
+11:
+12:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: valid S-o-b paragraph in the middle' '
+ append_signoff <<\EOF >actual &&
+subject
+
+Signed-off-by: my@house
+Signed-off-by: your@house
+
+A lot of houses.
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: my@house
+10:Signed-off-by: your@house
+11:
+13:
+14:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: the same signoff at the end' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: the same signoff at the end, no trailing NL' '
+ printf "subject\n\nSigned-off-by: C O Mitter <committer@example.com>" |
+ append_signoff >actual &&
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+9:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: the same signoff NOT at the end' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Signed-off-by: C O Mitter <committer@example.com>
+Signed-off-by: my@house
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+11:Signed-off-by: C O Mitter <committer@example.com>
+12:Signed-off-by: my@house
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: detect garbage in non-conforming footer' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Tested-by: my@house
+Some Trash
+Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+13:Signed-off-by: C O Mitter <committer@example.com>
+14:
+15:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
+test_expect_success 'signoff: footer begins with non-signoff without @ sign' '
+ append_signoff <<\EOF >actual &&
+subject
+
+body
+
+Reviewed-id: Noone
+Tested-by: my@house
+Change-id: Ideadbeef
+Signed-off-by: C O Mitter <committer@example.com>
+Bug: 1234
+EOF
+ cat >expected <<\EOF &&
+4:Subject: [PATCH] subject
+8:
+10:
+14:Signed-off-by: C O Mitter <committer@example.com>
+EOF
+ test_cmp expected actual
+'
+
test_expect_success 'format patch ignores color.ui' '
test_unconfig color.ui &&
git format-patch --stdout -1 >expect &&
s/song;/song();/
' <Beer.perl >Beer-correct.perl
-test_config () {
- git config "$1" "$2" &&
- test_when_finished "git config --unset $1"
-}
-
test_expect_funcname () {
lang=${2-java}
test_expect_code 1 git diff --no-index -U1 \
'
test_expect_success 'setup: remove diff driver regex' '
- test_might_fail git config --unset diff.testdriver.wordRegex
+ test_unconfig diff.testdriver.wordRegex
'
test_expect_success 'use configured regex' '
c
EOF
- test_when_finished "git config --unset diff.suppress-blank-empty" &&
- git config diff.suppress-blank-empty true &&
+ test_config diff.suppress-blank-empty true &&
word_diff --word-diff=plain
'
test_expect_success 'wordRegex for the first file does not apply to the second' '
echo "*.tex diff=tex" >.gitattributes &&
- git config diff.tex.wordRegex "[a-z]+|." &&
+ test_config diff.tex.wordRegex "[a-z]+|." &&
cat >expect <<-\EOF &&
diff --git a/a.tex b/a.tex
--- a/a.tex
test_description='combined diff'
. ./test-lib.sh
+. "$TEST_DIRECTORY"/diff-lib.sh
setup_helper () {
one=$1 branch=$2 side=$3 &&
grep "^::::::::::::::::::::::::::::::::::::::::[^:]" out
'
+test_expect_success 'setup combined ignore spaces' '
+ git checkout master &&
+ >test &&
+ git add test &&
+ git commit -m initial &&
+
+ tr -d Q <<-\EOF >test &&
+ always coalesce
+ eol space coalesce Q
+ space change coalesce
+ all spa ces coalesce
+ eol spaces Q
+ space change
+ all spa ces
+ EOF
+ git commit -m "test space change" -a &&
+
+ git checkout -b side HEAD^ &&
+ tr -d Q <<-\EOF >test &&
+ always coalesce
+ eol space coalesce
+ space change coalesce
+ all spaces coalesce
+ eol spaces
+ space change
+ all spaces
+ EOF
+ git commit -m "test other space changes" -a &&
+
+ test_must_fail git merge master &&
+ tr -d Q <<-\EOF >test &&
+ eol spaces Q
+ space change
+ all spa ces
+ EOF
+ git commit -m merged -a
+'
+
+test_expect_success 'check combined output (no ignore space)' '
+ git show >actual.tmp &&
+ sed -e "1,/^@@@/d" < actual.tmp >actual &&
+ tr -d Q <<-\EOF >expected &&
+ --always coalesce
+ - eol space coalesce
+ - space change coalesce
+ - all spaces coalesce
+ - eol spaces
+ - space change
+ - all spaces
+ -eol space coalesce Q
+ -space change coalesce
+ -all spa ces coalesce
+ + eol spaces Q
+ + space change
+ + all spa ces
+ EOF
+ compare_diff_patch expected actual
+'
+
+test_expect_success 'check combined output (ignore space at eol)' '
+ git show --ignore-space-at-eol >actual.tmp &&
+ sed -e "1,/^@@@/d" < actual.tmp >actual &&
+ tr -d Q <<-\EOF >expected &&
+ --always coalesce
+ --eol space coalesce
+ - space change coalesce
+ - all spaces coalesce
+ -space change coalesce
+ -all spa ces coalesce
+ eol spaces Q
+ - space change
+ - all spaces
+ + space change
+ + all spa ces
+ EOF
+ compare_diff_patch expected actual
+'
+
+test_expect_success 'check combined output (ignore space change)' '
+ git show -b >actual.tmp &&
+ sed -e "1,/^@@@/d" < actual.tmp >actual &&
+ tr -d Q <<-\EOF >expected &&
+ --always coalesce
+ --eol space coalesce
+ --space change coalesce
+ - all spaces coalesce
+ -all spa ces coalesce
+ eol spaces Q
+ space change
+ - all spaces
+ + all spa ces
+ EOF
+ compare_diff_patch expected actual
+'
+
+test_expect_success 'check combined output (ignore all spaces)' '
+ git show -w >actual.tmp &&
+ sed -e "1,/^@@@/d" < actual.tmp >actual &&
+ tr -d Q <<-\EOF >expected &&
+ --always coalesce
+ --eol space coalesce
+ --space change coalesce
+ --all spaces coalesce
+ eol spaces Q
+ space change
+ all spa ces
+ EOF
+ compare_diff_patch expected actual
+'
+
test_done
'
test_expect_success 'log.decorate configuration' '
- test_might_fail git config --unset-all log.decorate &&
-
git log --oneline >expect.none &&
git log --oneline --decorate >expect.short &&
git log --oneline --decorate=full >expect.full &&
git log --oneline >actual &&
test_cmp expect.short actual &&
- git config --unset-all log.decorate &&
- git config log.decorate true &&
+ test_config log.decorate true &&
git log --oneline >actual &&
test_cmp expect.short actual &&
git log --oneline --decorate=full >actual &&
git log --oneline --decorate=no >actual &&
test_cmp expect.none actual &&
- git config --unset-all log.decorate &&
- git config log.decorate no &&
+ test_config log.decorate no &&
git log --oneline >actual &&
test_cmp expect.none actual &&
git log --oneline --decorate >actual &&
git log --oneline --decorate=full >actual &&
test_cmp expect.full actual &&
- git config --unset-all log.decorate &&
- git config log.decorate 1 &&
+ test_config log.decorate 1 &&
git log --oneline >actual &&
test_cmp expect.short actual &&
git log --oneline --decorate=full >actual &&
git log --oneline --decorate=no >actual &&
test_cmp expect.none actual &&
- git config --unset-all log.decorate &&
- git config log.decorate short &&
+ test_config log.decorate short &&
git log --oneline >actual &&
test_cmp expect.short actual &&
git log --oneline --no-decorate >actual &&
git log --oneline --decorate=full >actual &&
test_cmp expect.full actual &&
- git config --unset-all log.decorate &&
- git config log.decorate full &&
+ test_config log.decorate full &&
git log --oneline >actual &&
test_cmp expect.full actual &&
git log --oneline --no-decorate >actual &&
git log --oneline --decorate >actual &&
test_cmp expect.short actual
- git config --unset-all log.decorate &&
+ test_unconfig log.decorate &&
git log --pretty=raw >expect.raw &&
- git config log.decorate full &&
+ test_config log.decorate full &&
git log --pretty=raw >actual &&
test_cmp expect.raw actual
'
test_expect_success 'reflog is expected format' '
- test_might_fail git config --remove-section log &&
git log -g --abbrev-commit --pretty=oneline >expect &&
git reflog >actual &&
test_cmp expect actual
'
test_expect_success 'log.abbrevCommit configuration' '
- test_when_finished "git config --unset log.abbrevCommit" &&
-
- test_might_fail git config --unset log.abbrevCommit &&
-
git log --abbrev-commit >expect.log.abbrev &&
git log --no-abbrev-commit >expect.log.full &&
git log --pretty=raw >expect.log.raw &&
git whatchanged --abbrev-commit >expect.whatchanged.abbrev &&
git whatchanged --no-abbrev-commit >expect.whatchanged.full &&
- git config log.abbrevCommit true &&
+ test_config log.abbrevCommit true &&
git log >actual &&
test_cmp expect.log.abbrev actual &&
test_description='git archive --format=zip test'
. ./test-lib.sh
-GIT_UNZIP=${GIT_UNZIP:-unzip}
SUBSTFORMAT=%H%n
-test_lazy_prereq UNZIP '
- "$GIT_UNZIP" -v
- test $? -ne 127
-'
-
test_lazy_prereq UNZIP_SYMLINKS '
(
mkdir unzip-symlinks &&
--- /dev/null
+#!/bin/sh
+
+test_description='test corner cases of git-archive'
+. ./test-lib.sh
+
+test_expect_success 'create commit with empty tree' '
+ git commit --allow-empty -m foo
+'
+
+# Make a dir and clean it up afterwards
+make_dir() {
+ mkdir "$1" &&
+ test_when_finished "rm -rf '$1'"
+}
+
+# Check that the dir given in "$1" contains exactly the
+# set of paths given as arguments.
+check_dir() {
+ dir=$1; shift
+ {
+ echo "$dir" &&
+ for i in "$@"; do
+ echo "$dir/$i"
+ done
+ } | sort >expect &&
+ find "$dir" -print | sort >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'tar archive of empty tree is empty' '
+ git archive --format=tar HEAD >empty.tar &&
+ make_dir extract &&
+ "$TAR" xf empty.tar -C extract &&
+ check_dir extract
+'
+
+test_expect_success 'tar archive of empty tree with prefix' '
+ git archive --format=tar --prefix=foo/ HEAD >prefix.tar &&
+ make_dir extract &&
+ "$TAR" xf prefix.tar -C extract &&
+ check_dir extract foo
+'
+
+test_expect_success UNZIP 'zip archive of empty tree is empty' '
+ # Detect the exit code produced when our particular flavor of unzip
+ # sees an empty archive. Infozip will generate a warning and exit with
+ # code 1. But in the name of sanity, we do not expect other unzip
+ # implementations to do the same thing (it would be perfectly
+ # reasonable to exit 0, for example).
+ #
+ # This makes our test less rigorous on some platforms (unzip may not
+ # handle the empty repo at all, making our later check of its exit code
+ # a no-op). But we cannot do anything reasonable except skip the test
+ # on such platforms anyway, and this is the moral equivalent.
+ "$GIT_UNZIP" "$TEST_DIRECTORY"/t5004/empty.zip
+ expect_code=$?
+
+ git archive --format=zip HEAD >empty.zip &&
+ make_dir extract &&
+ (
+ cd extract &&
+ test_expect_code $expect_code "$GIT_UNZIP" ../empty.zip
+ ) &&
+ check_dir extract
+'
+
+test_expect_success UNZIP 'zip archive of empty tree with prefix' '
+ # We do not have to play exit-code tricks here, because our
+ # result should not be empty; it has a directory in it.
+ git archive --format=zip --prefix=foo/ HEAD >prefix.zip &&
+ make_dir extract &&
+ (
+ cd extract &&
+ "$GIT_UNZIP" ../prefix.zip
+ ) &&
+ check_dir extract foo
+'
+
+test_expect_success 'archive complains about pathspec on empty tree' '
+ test_must_fail git archive --format=tar HEAD -- foo >/dev/null
+'
+
+test_expect_success 'create a commit with an empty subtree' '
+ empty_tree=$(git hash-object -t tree /dev/null) &&
+ root_tree=$(printf "040000 tree $empty_tree\tsub\n" | git mktree)
+'
+
+test_expect_success 'archive empty subtree with no pathspec' '
+ git archive --format=tar $root_tree >subtree-all.tar &&
+ make_dir extract &&
+ "$TAR" xf subtree-all.tar -C extract &&
+ check_dir extract sub
+'
+
+test_expect_success 'archive empty subtree by direct pathspec' '
+ git archive --format=tar $root_tree -- sub >subtree-path.tar &&
+ make_dir extract &&
+ "$TAR" xf subtree-path.tar -C extract &&
+ check_dir extract sub
+'
+
+test_done
test_cmp count7.expected count7.actual
'
+test_expect_success 'clone shallow with packed refs' '
+ git pack-refs --all &&
+ git clone --depth 1 --branch A "file://$(pwd)/." shallow8 &&
+ echo "in-pack: 4" > count8.expected &&
+ GIT_DIR=shallow8/.git git count-objects -v |
+ grep "^in-pack" > count8.actual &&
+ test_cmp count8.expected count8.actual
+'
+
test_expect_success 'setup tests for the --stdin parameter' '
for head in C D E F
do
. ./test-lib.sh
-if ! test_have_prereq NOT_MINGW; then
- say "GIT_DEBUG_SEND_PACK not supported - skipping tests"
-fi
-
# End state of the repository:
#
# T - tag1 S - tag2
# \ C - origin/cat \
# origin/master master
-test_expect_success NOT_MINGW setup '
+test_expect_success setup '
test_tick &&
echo ichi >file &&
git add file &&
'
U=UPLOAD_LOG
+UPATH="$(pwd)/$U"
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
cat - <<EOF >expect
-#S
want $A
-#E
EOF
'
-test_expect_success NOT_MINGW 'fetch A (new commit : 1 connection)' '
+get_needs () {
+ test -s "$1" &&
+ perl -alne '
+ next unless $F[1] eq "upload-pack<";
+ last if $F[2] eq "0000";
+ print $F[2], " ", $F[3];
+ ' "$1"
+}
+
+test_expect_success 'fetch A (new commit : 1 connection)' '
rm -f $U &&
(
cd cloned &&
- GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+ GIT_TRACE_PACKET=$UPATH git fetch &&
test $A = $(git rev-parse --verify origin/master)
) &&
- test -s $U &&
- cut -d" " -f1,2 $U >actual &&
+ get_needs $U >actual &&
test_cmp expect actual
'
-test_expect_success NOT_MINGW "create tag T on A, create C on branch cat" '
+test_expect_success "create tag T on A, create C on branch cat" '
git tag -a -m tag1 tag1 $A &&
T=$(git rev-parse --verify tag1) &&
git checkout master
'
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
cat - <<EOF >expect
-#S
want $C
want $T
-#E
EOF
'
-test_expect_success NOT_MINGW 'fetch C, T (new branch, tag : 1 connection)' '
+test_expect_success 'fetch C, T (new branch, tag : 1 connection)' '
rm -f $U &&
(
cd cloned &&
- GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+ GIT_TRACE_PACKET=$UPATH git fetch &&
test $C = $(git rev-parse --verify origin/cat) &&
test $T = $(git rev-parse --verify tag1) &&
test $A = $(git rev-parse --verify tag1^0)
) &&
- test -s $U &&
- cut -d" " -f1,2 $U >actual &&
+ get_needs $U >actual &&
test_cmp expect actual
'
-test_expect_success NOT_MINGW "create commits O, B, tag S on B" '
+test_expect_success "create commits O, B, tag S on B" '
test_tick &&
echo O >file &&
git add file &&
S=$(git rev-parse --verify tag2)
'
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
cat - <<EOF >expect
-#S
want $B
want $S
-#E
EOF
'
-test_expect_success NOT_MINGW 'fetch B, S (commit and tag : 1 connection)' '
+test_expect_success 'fetch B, S (commit and tag : 1 connection)' '
rm -f $U &&
(
cd cloned &&
- GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+ GIT_TRACE_PACKET=$UPATH git fetch &&
test $B = $(git rev-parse --verify origin/master) &&
test $B = $(git rev-parse --verify tag2^0) &&
test $S = $(git rev-parse --verify tag2)
) &&
- test -s $U &&
- cut -d" " -f1,2 $U >actual &&
+ get_needs $U >actual &&
test_cmp expect actual
'
-test_expect_success NOT_MINGW 'setup expect' '
+test_expect_success 'setup expect' '
cat - <<EOF >expect
-#S
want $B
want $S
-#E
EOF
'
-test_expect_success NOT_MINGW 'new clone fetch master and tags' '
+test_expect_success 'new clone fetch master and tags' '
git branch -D cat
rm -f $U
(
cd clone2 &&
git init &&
git remote add origin .. &&
- GIT_DEBUG_SEND_PACK=3 git fetch 3>../$U &&
+ GIT_TRACE_PACKET=$UPATH git fetch &&
test $B = $(git rev-parse --verify origin/master) &&
test $S = $(git rev-parse --verify tag2) &&
test $B = $(git rev-parse --verify tag2^0) &&
test $T = $(git rev-parse --verify tag1) &&
test $A = $(git rev-parse --verify tag1^0)
) &&
- test -s $U &&
- cut -d" " -f1,2 $U >actual &&
+ get_needs $U >actual &&
test_cmp expect actual
'
(
for ref in "$@"
do
- git push testrepo $the_first_commit:refs/$ref || {
- echo "Oops, push refs/$ref failure"
- exit 1
- }
+ git push testrepo $the_first_commit:refs/$ref ||
+ exit
done &&
cd testrepo &&
for ref in "$@"
do
- r=$(git show-ref -s --verify refs/$ref) &&
- test "z$r" = "z$the_first_commit" || {
- echo "Oops, refs/$ref is wrong"
- exit 1
- }
+ echo "$the_first_commit" >expect &&
+ git show-ref -s --verify refs/$ref >actual &&
+ test_cmp expect actual ||
+ exit
done &&
git fsck --full
)
check_push_result () {
(
cd testrepo &&
- it="$1" &&
- shift
+ echo "$1" >expect &&
+ shift &&
for ref in "$@"
do
- r=$(git show-ref -s --verify refs/$ref) &&
- test "z$r" = "z$it" || {
- echo "Oops, refs/$ref is wrong"
- exit 1
- }
+ git show-ref -s --verify refs/$ref >actual &&
+ test_cmp expect actual ||
+ exit
done &&
git fsck --full
)
cd testrepo &&
git fetch .. refs/heads/master:refs/remotes/origin/master &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
git fetch up &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
git fetch up &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
git config remote.up.fetch "refs/heads/*:refs/remotes/origin/*" &&
git fetch up &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
git push testrepo refs/heads/master:refs/remotes/origin/master &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
git push testrepo "refs/heads/*:refs/remotes/origin/*" &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
test_expect_success 'push with insteadOf' '
mk_empty &&
TRASH="$(pwd)/" &&
- git config "url.$TRASH.insteadOf" trash/ &&
+ test_config "url.$TRASH.insteadOf" trash/ &&
git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
test_expect_success 'push with pushInsteadOf' '
mk_empty &&
TRASH="$(pwd)/" &&
- git config "url.$TRASH.pushInsteadOf" trash/ &&
+ test_config "url.$TRASH.pushInsteadOf" trash/ &&
git push trash/testrepo refs/heads/master:refs/remotes/origin/master &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
test_expect_success 'push with pushInsteadOf and explicit pushurl (pushInsteadOf should not rewrite)' '
mk_empty &&
TRASH="$(pwd)/" &&
- git config "url.trash2/.pushInsteadOf" trash/ &&
- git config remote.r.url trash/wrong &&
- git config remote.r.pushurl "$TRASH/testrepo" &&
+ test_config "url.trash2/.pushInsteadOf" trash/ &&
+ test_config remote.r.url trash/wrong &&
+ test_config remote.r.pushurl "$TRASH/testrepo" &&
git push r refs/heads/master:refs/remotes/origin/master &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
-
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
)
'
test_expect_success 'push with ambiguity' '
mk_test heads/frotz tags/frotz &&
- if git push testrepo master:frotz
- then
- echo "Oops, should have failed"
- false
- else
- check_push_result $the_first_commit heads/frotz tags/frotz
- fi
+ test_must_fail git push testrepo master:frotz &&
+ check_push_result $the_first_commit heads/frotz tags/frotz
'
git checkout local &&
git reset --hard $the_first_commit
) &&
- git config remote.there.url testrepo &&
- git config remote.there.push HEAD &&
- git config branch.master.remote there &&
+ test_config remote.there.url testrepo &&
+ test_config remote.there.push HEAD &&
+ test_config branch.master.remote there &&
git push &&
check_push_result $the_commit heads/master &&
check_push_result $the_first_commit heads/local
'
-# clean up the cruft left with the previous one
-git config --remove-section remote.there
-git config --remove-section branch.master
-
test_expect_success 'push with config remote.*.pushurl' '
mk_test heads/master &&
git checkout master &&
- git config remote.there.url test2repo &&
- git config remote.there.pushurl testrepo &&
+ test_config remote.there.url test2repo &&
+ test_config remote.there.pushurl testrepo &&
git push there &&
check_push_result $the_commit heads/master
'
-# clean up the cruft left with the previous one
-git config --remove-section remote.there
-
test_expect_success 'push with dry-run' '
mk_test heads/master &&
(
cd testrepo &&
git fetch branch1 &&
- r=$(git show-ref -s --verify refs/heads/branch1) &&
- test "z$r" = "z$the_commit" &&
- test 1 = $(git for-each-ref refs/heads | wc -l)
+ echo "$the_commit commit refs/heads/branch1" >expect &&
+ git for-each-ref refs/heads >actual &&
+ test_cmp expect actual
) &&
git checkout master
'
(
cd testrepo &&
git fetch branch2 &&
- r=$(git show-ref -s --verify refs/heads/branch2) &&
- test "z$r" = "z$the_first_commit" &&
- test 1 = $(git for-each-ref refs/heads | wc -l)
+ echo "$the_first_commit commit refs/heads/branch2" >expect &&
+ git for-each-ref refs/heads >actual &&
+ test_cmp expect actual
) &&
git checkout master
'
git push branch1 &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/heads/master) &&
- test "z$r" = "z$the_first_commit" &&
- test 1 = $(git for-each-ref refs/heads | wc -l)
+ echo "$the_first_commit commit refs/heads/master" >expect &&
+ git for-each-ref refs/heads >actual &&
+ test_cmp expect actual
)
'
git push branch2 &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/heads/branch3) &&
- test "z$r" = "z$the_first_commit" &&
- test 1 = $(git for-each-ref refs/heads | wc -l)
+ echo "$the_first_commit commit refs/heads/branch3" >expect &&
+ git for-each-ref refs/heads >actual &&
+ test_cmp expect actual
) &&
git checkout master
'
git push >.git/bar --porcelain testrepo refs/heads/master:refs/remotes/origin/master &&
(
cd testrepo &&
- r=$(git show-ref -s --verify refs/remotes/origin/master) &&
- test "z$r" = "z$the_commit" &&
- test 1 = $(git for-each-ref refs/remotes/origin | wc -l)
+ echo "$the_commit commit refs/remotes/origin/master" >expect &&
+ git for-each-ref refs/remotes/origin >actual &&
+ test_cmp expect actual
) &&
test_cmp .git/foo .git/bar
'
)
'
+test_expect_success 'fetch follows tags by default' '
+ mk_test heads/master &&
+ rm -fr src dst &&
+ git init src &&
+ (
+ cd src &&
+ git pull ../testrepo master &&
+ git tag -m "annotated" tag &&
+ git for-each-ref >tmp1 &&
+ (
+ cat tmp1
+ sed -n "s|refs/heads/master$|refs/remotes/origin/master|p" tmp1
+ ) |
+ sort -k 3 >../expect
+ ) &&
+ git init dst &&
+ (
+ cd dst &&
+ git remote add origin ../src &&
+ git config branch.master.remote origin &&
+ git config branch.master.merge refs/heads/master &&
+ git pull &&
+ git for-each-ref >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push does not follow tags by default' '
+ mk_test heads/master &&
+ rm -fr src dst &&
+ git init src &&
+ git init --bare dst &&
+ (
+ cd src &&
+ git pull ../testrepo master &&
+ git tag -m "annotated" tag &&
+ git checkout -b another &&
+ git commit --allow-empty -m "future commit" &&
+ git tag -m "future" future &&
+ git checkout master &&
+ git for-each-ref refs/heads/master >../expect &&
+ git push ../dst master
+ ) &&
+ (
+ cd dst &&
+ git for-each-ref >../actual
+ ) &&
+ test_cmp expect actual
+'
+
+test_expect_success 'push --follow-tag only pushes relevant tags' '
+ mk_test heads/master &&
+ rm -fr src dst &&
+ git init src &&
+ git init --bare dst &&
+ (
+ cd src &&
+ git pull ../testrepo master &&
+ git tag -m "annotated" tag &&
+ git checkout -b another &&
+ git commit --allow-empty -m "future commit" &&
+ git tag -m "future" future &&
+ git checkout master &&
+ git for-each-ref refs/heads/master refs/tags/tag >../expect
+ git push --follow-tag ../dst master
+ ) &&
+ (
+ cd dst &&
+ git for-each-ref >../actual
+ ) &&
+ test_cmp expect actual
+'
+
test_done
'
test_expect_success 'pull.rebase' '
git reset --hard before-rebase &&
- git config --bool pull.rebase true &&
- test_when_finished "git config --unset pull.rebase" &&
+ test_config pull.rebase true &&
git pull . copy &&
test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
test new = $(git show HEAD:file2)
test_expect_success 'branch.to-rebase.rebase' '
git reset --hard before-rebase &&
- git config --bool branch.to-rebase.rebase true &&
- test_when_finished "git config --unset branch.to-rebase.rebase" &&
+ test_config branch.to-rebase.rebase true &&
git pull . copy &&
test $(git rev-parse HEAD^) = $(git rev-parse copy) &&
test new = $(git show HEAD:file2)
test_expect_success 'branch.to-rebase.rebase should override pull.rebase' '
git reset --hard before-rebase &&
- git config --bool pull.rebase true &&
- test_when_finished "git config --unset pull.rebase" &&
- git config --bool branch.to-rebase.rebase false &&
- test_when_finished "git config --unset branch.to-rebase.rebase" &&
+ test_config pull.rebase true &&
+ test_config branch.to-rebase.rebase false &&
git pull . copy &&
test $(git rev-parse HEAD^) != $(git rev-parse copy) &&
test new = $(git show HEAD:file2)
git update-ref refs/remotes/me/copy copy^ &&
COPY=$(git rev-parse --verify me/copy) &&
git rebase --onto $COPY copy &&
- git config branch.to-rebase.remote me &&
- git config branch.to-rebase.merge refs/heads/copy &&
- git config branch.to-rebase.rebase true &&
+ test_config branch.to-rebase.remote me &&
+ test_config branch.to-rebase.merge refs/heads/copy &&
+ test_config branch.to-rebase.rebase true &&
echo dirty >> file &&
git add file &&
test_must_fail git pull &&
test ! -s out)
'
+test_expect_success 'git pull -q --rebase' '
+ mkdir clonedqrb &&
+ (cd clonedqrb && git init &&
+ git pull -q --rebase "../parent" >out 2>err &&
+ test ! -s err &&
+ test ! -s out &&
+ git pull -q --rebase "../parent" >out 2>err &&
+ test ! -s err &&
+ test ! -s out)
+'
+
test_expect_success 'git pull' '
mkdir cloned &&
(cd cloned && git init &&
test ! -s out)
'
+test_expect_success 'git pull --rebase' '
+ mkdir clonedrb &&
+ (cd clonedrb && git init &&
+ git pull --rebase "../parent" >out 2>err &&
+ test -s err &&
+ test ! -s out)
+'
+
test_expect_success 'git pull -v' '
mkdir clonedv &&
(cd clonedv && git init &&
test ! -s out)
'
+test_expect_success 'git pull -v --rebase' '
+ mkdir clonedvrb &&
+ (cd clonedvrb && git init &&
+ git pull -v --rebase "../parent" >out 2>err &&
+ test -s err &&
+ test ! -s out)
+'
+
test_expect_success 'git pull -v -q' '
mkdir clonedvq &&
(cd clonedvq && git init &&
git checkout master &&
test_commit commit path3 &&
HEAD=$(git rev-parse --verify HEAD) &&
- git config http.postbuffer 4 &&
- test_when_finished "git config --unset http.postbuffer" &&
+ test_config http.postbuffer 4 &&
git push -v -v origin $BRANCH 2>err &&
grep "POST git-receive-pack (chunked)" err &&
(cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
rm -f "$U.D"
-test_expect_success 'cloning with reference (no -l -s)' \
-'GIT_DEBUG_SEND_PACK=3 git clone --reference B "file://$(pwd)/A" D 3>"$U.D"'
+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' \
-'! grep "^want" "$U.D"'
+test_expect_success 'fetched no objects' '
+ test -s "$U.D" &&
+ ! grep " want" "$U.D"
+'
cd "$base_dir"
(
cd K &&
git remote add J "file://$base_dir/J" &&
- GIT_DEBUG_SEND_PACK=3 git fetch J 3>"$U.K"
+ GIT_TRACE_PACKET=$U.K git fetch J
) &&
master_object=$(cd A && git for-each-ref --format="%(objectname)" refs/heads/master) &&
- ! grep "^want $master_object" "$U.K" &&
+ 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"
+ ! grep " want $tag_object" "$U.K"
'
test_done
check_revlist "--min-parents=13" &&
check_revlist "--min-parents=4 --max-parents=11" tetrapus
'
+
+test_expect_success 'ancestors with the same commit time' '
+
+ test_tick_keep=$test_tick &&
+ for i in 1 2 3 4 5 6 7 8; do
+ test_tick=$test_tick_keep
+ test_commit t$i
+ done &&
+ git rev-list t1^! --not t$i >result &&
+ >expect &&
+ test_cmp expect result
+'
+
test_done
echo "Final change" >file &&
test_tick && git commit -a -m "Final change" &&
- note I
+ note I &&
+
+ git symbolic-ref HEAD refs/heads/unrelated &&
+ git rm -f "*" &&
+ echo "Unrelated branch" >side &&
+ git add side &&
+ test_tick && git commit -m "Side root" &&
+ note J &&
+
+ git checkout master &&
+ test_tick && git merge -m "Coolest" unrelated &&
+ note K &&
+
+ echo "Immaterial" >elif &&
+ git add elif &&
+ test_tick && git commit -m "Last" &&
+ note L
'
FMT='tformat:%P %H | %s'
'
}
-check_result 'I H G F E D C B A' --full-history
-check_result 'I H E C B A' --full-history -- file
-check_result 'I H E C B A' --full-history --topo-order -- file
-check_result 'I H E C B A' --full-history --date-order -- file
+check_result 'L K J I H G F E D C B A' --full-history
+check_result 'K I H E C B A' --full-history -- file
+check_result 'K I H E C B A' --full-history --topo-order -- file
+check_result 'K I H E C B A' --full-history --date-order -- file
check_result 'I E C B A' --simplify-merges -- file
check_result 'I B A' -- file
check_result 'I B A' --topo-order -- file
cp .git/BISECT_START saved &&
test_must_fail git bisect start $HASH4 foo -- &&
git branch > branch.output &&
- test_i18ngrep "* (no branch)" branch.output > /dev/null &&
+ test_i18ngrep "* (no branch, bisect started on other)" branch.output > /dev/null &&
test_cmp saved .git/BISECT_START
'
test_expect_success 'bisect start: no ".git/BISECT_START" if mistaken rev' '
tag_exists myhead
'
+test_expect_success '--force can create a tag with the name of one existing' '
+ tag_exists mytag &&
+ git tag --force mytag &&
+ tag_exists mytag'
+
+test_expect_success '--force is moot with a non-existing tag name' '
+ git tag newtag >expect &&
+ git tag --force forcetag >actual &&
+ test_cmp expect actual
+'
+git tag -d newtag forcetag
+
# deleting tags:
test_expect_success 'trying to delete an unknown tag should fail' '
--- /dev/null
+#!/bin/sh
+
+test_description='git-status with core.ignorecase=true'
+
+. ./test-lib.sh
+
+test_expect_success 'status with hash collisions' '
+ # note: "V/", "V/XQANY/" and "WURZAUP/" produce the same hash code
+ # in name-hash.c::hash_name
+ mkdir V &&
+ mkdir V/XQANY &&
+ mkdir WURZAUP &&
+ touch V/XQANY/test &&
+ git config core.ignorecase true &&
+ git add . &&
+ # test is successful if git status completes (no endless loop)
+ git status
+'
+
+test_done
)
'
+test_expect_success 'set up a second submodule' '
+ git submodule add ./init2 example2 &&
+ git commit -m "submodule example2 added"
+'
+
+test_expect_success 'submodule deinit should remove the whole submodule section from .git/config' '
+ git config submodule.example.foo bar &&
+ git config submodule.example2.frotz nitfol &&
+ git submodule deinit init &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ test -n "$(git config --get-regexp "submodule\.example2\.")" &&
+ test -f example2/.git &&
+ rmdir init
+'
+
+test_expect_success 'submodule deinit . deinits all initialized submodules' '
+ git submodule update --init &&
+ git config submodule.example.foo bar &&
+ git config submodule.example2.frotz nitfol &&
+ test_must_fail git submodule deinit &&
+ git submodule deinit . &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ test -z "$(git config --get-regexp "submodule\.example2\.")" &&
+ rmdir init example2
+'
+
+test_expect_success 'submodule deinit deinits a submodule when its work tree is missing or empty' '
+ git submodule update --init &&
+ rm -rf init example2/* example2/.git &&
+ git submodule deinit init example2 &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ test -z "$(git config --get-regexp "submodule\.example2\.")" &&
+ rmdir init
+'
+
+test_expect_success 'submodule deinit fails when the submodule contains modifications unless forced' '
+ git submodule update --init &&
+ echo X >>init/s &&
+ test_must_fail git submodule deinit init &&
+ test -n "$(git config --get-regexp "submodule\.example\.")" &&
+ test -f example2/.git &&
+ git submodule deinit -f init &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ rmdir init
+'
+
+test_expect_success 'submodule deinit fails when the submodule contains untracked files unless forced' '
+ git submodule update --init &&
+ echo X >>init/untracked &&
+ test_must_fail git submodule deinit init &&
+ test -n "$(git config --get-regexp "submodule\.example\.")" &&
+ test -f example2/.git &&
+ git submodule deinit -f init &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ rmdir init
+'
+
+test_expect_success 'submodule deinit fails when the submodule HEAD does not match unless forced' '
+ git submodule update --init &&
+ (
+ cd init &&
+ git checkout HEAD^
+ ) &&
+ test_must_fail git submodule deinit init &&
+ test -n "$(git config --get-regexp "submodule\.example\.")" &&
+ test -f example2/.git &&
+ git submodule deinit -f init &&
+ test -z "$(git config --get-regexp "submodule\.example\.")" &&
+ rmdir init
+'
+
+test_expect_success 'submodule deinit is silent when used on an uninitialized submodule' '
+ git submodule update --init &&
+ git submodule deinit init >actual &&
+ test_i18ngrep "Submodule .example. (.*) unregistered for path .init" actual &&
+ git submodule deinit init >actual &&
+ test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+ git submodule deinit . >actual &&
+ test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+ test_i18ngrep "Submodule .example2. (.*) unregistered for path .example2" actual &&
+ git submodule deinit . >actual &&
+ test_i18ngrep ! "Submodule .example. (.*) unregistered for path .init" actual &&
+ test_i18ngrep ! "Submodule .example2. (.*) unregistered for path .example2" actual &&
+ rmdir init example2
+'
+
+test_expect_success 'submodule deinit fails when submodule has a .git directory even when forced' '
+ git submodule update --init &&
+ (
+ cd init &&
+ rm .git &&
+ cp -R ../.git/modules/example .git &&
+ GIT_WORK_TREE=. git config --unset core.worktree
+ ) &&
+ test_must_fail git submodule deinit init &&
+ test_must_fail git submodule deinit -f init &&
+ test -d init/.git &&
+ test -n "$(git config --get-regexp "submodule\.example\.")"
+'
+
test_done
test_expect_success 'submodule update properly revives a moved submodule' '
(cd super &&
+ H=$(git rev-parse --short HEAD) &&
git commit -am "pre move" &&
- git status >expect&&
+ H2=$(git rev-parse --short HEAD) &&
+ git status | sed "s/$H/XXX/" >expect &&
H=$(cd submodule2; git rev-parse HEAD) &&
git rm --cached submodule2 &&
rm -rf submodule2 &&
git config -f .gitmodules submodule.submodule2.path "moved/sub module"
git commit -am "post move" &&
git submodule update &&
- git status >actual &&
+ git status | sed "s/$H2/XXX/" >actual &&
test_cmp expect actual
)
'
'
test_expect_success 'nonexistent template file in config should return error' '
- git config commit.template "$PWD"/notexist &&
- test_when_finished "git config --unset commit.template" &&
+ test_config commit.template "$PWD"/notexist &&
(
GIT_EDITOR="echo hello >\"\$1\"" &&
export GIT_EDITOR &&
test_expect_success 'config-specified template should commit' '
echo "new template" > "$TEMPLATE" &&
- git config commit.template "$TEMPLATE" &&
+ test_config commit.template "$TEMPLATE" &&
echo "more content" >> foo &&
git add foo &&
(
test_set_editor "$TEST_DIRECTORY"/t7500/add-content &&
git commit
) &&
- git config --unset commit.template &&
commit_msg_is "new templatecommit message"
'
test_expect_success 'verbose respects diff config' '
- git config color.diff always &&
+ test_config color.diff always &&
git status -v >actual &&
- grep "\[1mdiff --git" actual &&
- git config --unset color.diff
+ grep "\[1mdiff --git" actual
'
mesg_with_comment_and_newlines='
'
+test_expect_success 'commit -s places sob on third line after two empty lines' '
+ git commit -s --allow-empty --allow-empty-message &&
+ cat <<-EOF >expect &&
+
+
+ Signed-off-by: $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL>
+
+ EOF
+ sed -e "/^#/d" -e "s/^:.*//" .git/COMMIT_EDITMSG >actual &&
+ test_cmp expect actual
+'
+
write_script .git/FAKE_EDITOR <<\EOF
mv "$1" "$1.orig"
(
echo '## Custom template' >template
-clear_config () {
- (
- git config --unset-all "$1"
- case $? in
- 0|5) exit 0 ;;
- *) exit 1 ;;
- esac
- )
-}
-
try_commit () {
git reset --hard &&
echo >>negative &&
try_commit_status_combo () {
test_expect_success 'commit' '
- clear_config commit.status &&
try_commit "" &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit' '
- clear_config commit.status &&
try_commit "" &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --status' '
- clear_config commit.status &&
try_commit --status &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --no-status' '
- clear_config commit.status &&
try_commit --no-status &&
test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit with commit.status = yes' '
- clear_config commit.status &&
- git config commit.status yes &&
+ test_config commit.status yes &&
try_commit "" &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit with commit.status = no' '
- clear_config commit.status &&
- git config commit.status no &&
+ test_config commit.status no &&
try_commit "" &&
test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --status with commit.status = yes' '
- clear_config commit.status &&
- git config commit.status yes &&
+ test_config commit.status yes &&
try_commit --status &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --no-status with commit.status = yes' '
- clear_config commit.status &&
- git config commit.status yes &&
+ test_config commit.status yes &&
try_commit --no-status &&
test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --status with commit.status = no' '
- clear_config commit.status &&
- git config commit.status no &&
+ test_config commit.status no &&
try_commit --status &&
test_i18ngrep "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
test_expect_success 'commit --no-status with commit.status = no' '
- clear_config commit.status &&
- git config commit.status no &&
+ test_config commit.status no &&
try_commit --no-status &&
test_i18ngrep ! "^# Changes to be committed:" .git/COMMIT_EDITMSG
'
try_commit_status_combo
test_expect_success 'commit --status with custom comment character' '
- test_when_finished "git config --unset core.commentchar" &&
- git config core.commentchar ";" &&
+ test_config core.commentchar ";" &&
try_commit --status &&
test_i18ngrep "^; Changes to be committed:" .git/COMMIT_EDITMSG
'
EOF
test_expect_success 'status (advice.statusHints false)' '
- test_when_finished "git config --unset advice.statusHints" &&
- git config advice.statusHints false &&
+ test_config advice.statusHints false &&
git status >output &&
test_i18ncmp expect output
'
test_expect_success 'status (status.showUntrackedFiles no)' '
- git config status.showuntrackedfiles no
- test_when_finished "git config --unset status.showuntrackedfiles" &&
+ test_config status.showuntrackedfiles no &&
git status >output &&
test_i18ncmp expect output
'
#
# Untracked files not listed
EOF
-git config advice.statusHints false
test_expect_success 'status -uno (advice.statusHints false)' '
+ test_config advice.statusHints false &&
git status -uno >output &&
test_i18ncmp expect output
'
-git config --unset advice.statusHints
cat >expect << EOF
M dir1/modified
'
test_expect_success 'status (status.showUntrackedFiles normal)' '
- git config status.showuntrackedfiles normal
- test_when_finished "git config --unset status.showuntrackedfiles" &&
+ test_config status.showuntrackedfiles normal
git status >output &&
test_i18ncmp expect output
'
'
test_expect_success 'status (status.showUntrackedFiles all)' '
- git config status.showuntrackedfiles all
- test_when_finished "git config --unset status.showuntrackedfiles" &&
+ test_config status.showuntrackedfiles all
git status >output &&
test_i18ncmp expect output
'
test_cmp expect output
'
test_expect_success 'status -s (status.showUntrackedFiles all)' '
- git config status.showuntrackedfiles all
+ test_config status.showuntrackedfiles all &&
git status -s >output &&
rm -rf dir3 &&
- git config --unset status.showuntrackedfiles &&
test_cmp expect output
'
EOF
test_expect_success 'status with color.ui' '
- git config color.ui always &&
- test_when_finished "git config --unset color.ui" &&
+ test_config color.ui always &&
git status | test_decode_color >output &&
test_i18ncmp expect output
'
test_expect_success 'status with color.status' '
- git config color.status always &&
- test_when_finished "git config --unset color.status" &&
+ test_config color.status always &&
git status | test_decode_color >output &&
test_i18ncmp expect output
'
test_expect_success 'status without relative paths' '
- git config status.relativePaths false &&
- test_when_finished "git config --unset status.relativePaths" &&
+ test_config status.relativePaths false &&
(cd dir1 && git status) >output &&
test_i18ncmp expect output
test_expect_success 'status -s without relative paths' '
- git config status.relativePaths false &&
- test_when_finished "git config --unset status.relativePaths" &&
+ test_config status.relativePaths false &&
(cd dir1 && git status -s) >output &&
test_cmp expect output
'
test_expect_success '.gitmodules ignore=untracked suppresses submodules with untracked content' '
- git config diff.ignoreSubmodules dirty &&
+ test_config diff.ignoreSubmodules dirty &&
git status >output &&
test_i18ncmp expect output &&
git config --add -f .gitmodules submodule.subname.ignore untracked &&
git config --add -f .gitmodules submodule.subname.path sm &&
git status >output &&
test_i18ncmp expect output &&
- git config -f .gitmodules --remove-section submodule.subname &&
- git config --unset diff.ignoreSubmodules
+ git config -f .gitmodules --remove-section submodule.subname
'
test_expect_success '.git/config ignore=untracked suppresses submodules with untracked content' '
'
test_expect_success '.gitmodules ignore=dirty suppresses submodules with untracked content' '
- git config diff.ignoreSubmodules dirty &&
+ test_config diff.ignoreSubmodules dirty &&
git status >output &&
! test -s actual &&
git config --add -f .gitmodules submodule.subname.ignore dirty &&
git config --add -f .gitmodules submodule.subname.path sm &&
git status >output &&
test_i18ncmp expect output &&
- git config -f .gitmodules --remove-section submodule.subname &&
- git config --unset diff.ignoreSubmodules
+ git config -f .gitmodules --remove-section submodule.subname
'
test_expect_success '.git/config ignore=dirty suppresses submodules with untracked content' '
EOF
test_expect_success "status (core.commentchar with submodule summary)" '
- test_when_finished "git config --unset core.commentchar" &&
- git config core.commentchar ";" &&
+ test_config core.commentchar ";" &&
git status >output &&
test_i18ncmp expect output
'
test_expect_success "status (core.commentchar with two chars with submodule summary)" '
- test_when_finished "git config --unset core.commentchar" &&
- git config core.commentchar ";;" &&
+ test_config core.commentchar ";;" &&
git status >output &&
test_i18ncmp expect output
'
ONTO=$(git rev-parse --short HEAD^^) &&
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached at $ONTO
# You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
# (fix conflicts and then run "git rebase --continue")
# (use "git rebase --skip" to skip this patch)
echo three >main.txt &&
git add main.txt &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached at $ONTO
# You are currently rebasing branch '\''rebase_conflicts'\'' on '\''$ONTO'\''.
# (all conflicts fixed: run "git rebase --continue")
#
ONTO=$(git rev-parse --short rebase_i_conflicts) &&
test_must_fail git rebase -i rebase_i_conflicts &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached at $ONTO
# You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
# (fix conflicts and then run "git rebase --continue")
# (use "git rebase --skip" to skip this patch)
test_must_fail git rebase -i rebase_i_conflicts &&
git add main.txt &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached at $ONTO
# You are currently rebasing branch '\''rebase_i_conflicts_second'\'' on '\''$ONTO'\''.
# (all conflicts fixed: run "git rebase --continue")
#
export FAKE_LINES &&
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD~2) &&
+ TGT=$(git rev-parse --short two_rebase_i) &&
git rebase -i HEAD~2 &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $TGT
# You are currently editing a commit while rebasing branch '\''rebase_i_edit'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
ONTO=$(git rev-parse --short HEAD~3) &&
git rebase -i HEAD~3 &&
git reset HEAD^ &&
+ TGT=$(git rev-parse --short HEAD) &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached at $TGT
# You are currently splitting a commit while rebasing branch '\''split_commit'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
export FAKE_LINES &&
test_when_finished "git rebase --abort" &&
ONTO=$(git rev-parse --short HEAD~3) &&
+ TGT=$(git rev-parse --short three_amend) &&
git rebase -i HEAD~3 &&
git commit --amend -m "foo" &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $TGT
# You are currently editing a commit while rebasing branch '\''amend_last'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git rebase -i HEAD~3 &&
git rebase --continue &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git rebase --continue &&
git reset HEAD^ &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
git rebase --continue &&
git commit --amend -m "foo" &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git commit --amend -m "a" &&
git rebase --continue &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git rebase --continue &&
git reset HEAD^ &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
git rebase --continue &&
git commit --amend -m "d" &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git commit -m "e" &&
git rebase --continue &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git rebase --continue &&
git reset HEAD^ &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently splitting a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (Once your working directory is clean, run "git rebase --continue")
#
git rebase --continue &&
git commit --amend -m "h" &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached from $ONTO
# You are currently editing a commit while rebasing branch '\''several_edits'\'' on '\''$ONTO'\''.
# (use "git commit --amend" to amend the current commit)
# (use "git rebase --continue" once you are satisfied with your changes)
git bisect start &&
git bisect bad &&
git bisect good one_bisect &&
- cat >expected <<-\EOF &&
- # Not currently on any branch.
- # You are currently bisecting branch '\''bisect'\''.
+ TGT=$(git rev-parse --short two_bisect) &&
+ cat >expected <<-EOF &&
+ # HEAD detached at $TGT
+ # You are currently bisecting, started from branch '\''bisect'\''.
# (use "git bisect reset" to get back to the original branch)
#
nothing to commit (use -u to show untracked files)
ONTO=$(git rev-parse --short HEAD^^) &&
test_must_fail git rebase HEAD^ --onto HEAD^^ &&
cat >expected <<-EOF &&
- # Not currently on any branch.
+ # HEAD detached at $ONTO
# You are currently rebasing branch '\''statushints_disabled'\'' on '\''$ONTO'\''.
#
# Unmerged paths:
test_i18ncmp expected actual
'
+test_expect_success 'status showing detached from a tag' '
+ test_commit atag tagging &&
+ git checkout atag &&
+ cat >expected <<-\EOF
+ # HEAD detached at atag
+ nothing to commit (use -u to show untracked files)
+ EOF
+ git status --untracked-files=no >actual &&
+ test_i18ncmp expected actual
+'
test_done
echo &&
git log --no-merges ^HEAD c2 c3
} >squash.1-5-9 &&
- echo >msg.nolog &&
+ : >msg.nologff &&
+ echo >msg.nolognoff &&
{
echo "* tag 'c3':" &&
echo " commit 3" &&
test_expect_success 'merges with merge.ff=only' '
git reset --hard c1 &&
test_tick &&
- test_when_finished "git config --unset merge.ff" &&
- git config merge.ff only &&
+ test_config merge.ff "only" &&
test_must_fail git merge c2 &&
test_must_fail git merge c3 &&
test_must_fail git merge c2 c3 &&
test_expect_success 'merge c1 with c2 (no-commit in config)' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "--no-commit" &&
+ test_config branch.master.mergeoptions "--no-commit" &&
git merge c2 &&
verify_merge file result.1-5 &&
verify_head $c1 &&
test_debug 'git log --graph --decorate --oneline --all'
test_expect_success 'merge c1 with c2 (log in config)' '
- git config branch.master.mergeoptions "" &&
git reset --hard c1 &&
git merge --log c2 &&
git show -s --pretty=tformat:%s%n%b >expect &&
- git config branch.master.mergeoptions --log &&
+ test_config branch.master.mergeoptions "--log" &&
git reset --hard c1 &&
git merge c2 &&
git show -s --pretty=tformat:%s%n%b >actual &&
'
test_expect_success 'merge c1 with c2 (log in config gets overridden)' '
- test_when_finished "git config --remove-section branch.master" &&
- test_when_finished "git config --remove-section merge" &&
- test_might_fail git config --remove-section branch.master &&
- test_might_fail git config --remove-section merge &&
-
git reset --hard c1 &&
git merge c2 &&
git show -s --pretty=tformat:%s%n%b >expect &&
- git config branch.master.mergeoptions "--no-log" &&
- git config merge.log true &&
+ test_config branch.master.mergeoptions "--no-log" &&
+ test_config merge.log "true" &&
git reset --hard c1 &&
git merge c2 &&
git show -s --pretty=tformat:%s%n%b >actual &&
test_expect_success 'merge c1 with c2 (squash in config)' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "--squash" &&
+ test_config branch.master.mergeoptions "--squash" &&
git merge c2 &&
verify_merge file result.1-5 &&
verify_head $c1 &&
test_expect_success 'override config option -n with --summary' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "-n" &&
+ test_config branch.master.mergeoptions "-n" &&
test_tick &&
git merge --summary c2 >diffstat.txt &&
verify_merge file result.1-5 msg.1-5 &&
test_expect_success 'override config option -n with --stat' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "-n" &&
+ test_config branch.master.mergeoptions "-n" &&
test_tick &&
git merge --stat c2 >diffstat.txt &&
verify_merge file result.1-5 msg.1-5 &&
test_expect_success 'override config option --stat' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "--stat" &&
+ test_config branch.master.mergeoptions "--stat" &&
test_tick &&
git merge -n c2 >diffstat.txt &&
verify_merge file result.1-5 msg.1-5 &&
test_expect_success 'merge c1 with c2 (override --no-commit)' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "--no-commit" &&
+ test_config branch.master.mergeoptions "--no-commit" &&
test_tick &&
git merge --commit c2 &&
verify_merge file result.1-5 msg.1-5 &&
test_expect_success 'merge c1 with c2 (override --squash)' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "--squash" &&
+ test_config branch.master.mergeoptions "--squash" &&
test_tick &&
git merge --no-squash c2 &&
verify_merge file result.1-5 msg.1-5 &&
test_expect_success 'merge c0 with c1 (no-ff)' '
git reset --hard c0 &&
- git config branch.master.mergeoptions "" &&
test_tick &&
git merge --no-ff c1 &&
verify_merge file result.1 &&
test_expect_success 'merge c0 with c1 (merge.ff=false)' '
git reset --hard c0 &&
- git config merge.ff false &&
+ test_config merge.ff "false" &&
test_tick &&
git merge c1 &&
- git config --remove-section merge &&
verify_merge file result.1 &&
verify_parents $c0 $c1
'
test_expect_success 'combine branch.master.mergeoptions with merge.ff' '
git reset --hard c0 &&
- git config branch.master.mergeoptions --ff &&
- git config merge.ff false &&
+ test_config branch.master.mergeoptions "--ff" &&
+ test_config merge.ff "false" &&
test_tick &&
git merge c1 &&
- git config --remove-section "branch.master" &&
- git config --remove-section "merge" &&
verify_merge file result.1 &&
verify_parents "$c0"
'
test_expect_success 'tolerate unknown values for merge.ff' '
git reset --hard c0 &&
- git config merge.ff something-new &&
+ test_config merge.ff "something-new" &&
test_tick &&
git merge c1 2>message &&
- git config --remove-section "merge" &&
verify_head "$c1" &&
test_cmp empty message
'
test_expect_success 'merge c0 with c1 (ff overrides no-ff)' '
git reset --hard c0 &&
- git config branch.master.mergeoptions "--no-ff" &&
+ test_config branch.master.mergeoptions "--no-ff" &&
git merge --ff c1 &&
verify_merge file result.1 &&
verify_head $c1
git reset --hard c0 &&
git merge --no-log c2 &&
git show -s --pretty=format:%b HEAD >msg.act &&
- test_cmp msg.nolog msg.act &&
+ test_cmp msg.nologff msg.act &&
+
+ git reset --hard c0 &&
+ test_config branch.master.mergeoptions "--no-ff" &&
+ git merge --no-log c2 &&
+ git show -s --pretty=format:%b HEAD >msg.act &&
+ test_cmp msg.nolognoff msg.act &&
git merge --log c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
test_cmp msg.log msg.act &&
git reset --hard HEAD^ &&
- git config merge.log yes &&
+ test_config merge.log "yes" &&
git merge c3 &&
git show -s --pretty=format:%b HEAD >msg.act &&
test_cmp msg.log msg.act
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "" &&
test_tick &&
git merge c0 c2 c0 c1 &&
verify_merge file result.1-5 &&
test_expect_success 'merge c1 with c0, c2, c0, and c1' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "" &&
test_tick &&
git merge c0 c2 c0 c1 &&
verify_merge file result.1-5 &&
test_expect_success 'merge c1 with c1 and c2' '
git reset --hard c1 &&
- git config branch.master.mergeoptions "" &&
test_tick &&
git merge c1 c2 &&
verify_merge file result.1-5 &&
stdin_contains file <output
'
+write_script .git/CHECK_SYMLINKS <<\EOF
+for f in file file2 sub/sub
+do
+ echo "$f"
+ readlink "$2/$f"
+done >actual
+EOF
+
+test_expect_success PERL,SYMLINKS 'difftool --dir-diff --symlink without unstaged changes' '
+ cat >expect <<-EOF &&
+ file
+ $(pwd)/file
+ file2
+ $(pwd)/file2
+ sub/sub
+ $(pwd)/sub/sub
+ EOF
+ git difftool --dir-diff --symlink \
+ --extcmd "./.git/CHECK_SYMLINKS" branch HEAD &&
+ test_cmp actual expect
+'
+
test_expect_success PERL 'difftool --dir-diff ignores --prompt' '
git difftool --dir-diff --prompt --extcmd ls branch >output &&
stdin_contains sub <output &&
test_cmp empty out
'
-test_config() {
- git config "$1" "$2" &&
- test_when_finished "git config --unset $1"
-}
-
test_expect_success 'copes with color settings' '
rm -f actual &&
echo grep.h >expect &&
test_when_finished "GIT_COMMITTER_NAME=\"C O Mitter\"" &&
echo "ISO-8859-1" >> file &&
git add file &&
- git config i18n.commitencoding ISO-8859-1 &&
- test_when_finished "git config --unset i18n.commitencoding" &&
+ test_config i18n.commitencoding ISO-8859-1 &&
git commit -F "$TEST_DIRECTORY"/t3900/ISO8859-1.txt &&
gitweb_run "p=.git;a=commit"'
)
'
+# Common setup using .p4config to set P4CLIENT and P4PORT breaks
+# if clone destination is relative. Make sure that chdir() expands
+# the relative path in --dest to absolute.
+test_expect_success 'p4 client root would be relative due to clone --dest' '
+ test_when_finished cleanup_git &&
+ (
+ echo P4PORT=$P4PORT >git/.p4config &&
+ P4CONFIG=.p4config &&
+ export P4CONFIG &&
+ unset P4PORT &&
+ git p4 clone --dest="git" //depot
+ )
+'
+
+# When the p4 client Root is a symlink, make sure chdir() does not use
+# getcwd() to convert it to a physical path.
+test_expect_success SYMLINKS 'p4 client root symlink should stay symbolic' '
+ physical="$TRASH_DIRECTORY/physical" &&
+ symbolic="$TRASH_DIRECTORY/symbolic" &&
+ test_when_finished "rm -rf \"$physical\"" &&
+ test_when_finished "rm \"$symbolic\"" &&
+ mkdir -p "$physical" &&
+ ln -s "$physical" "$symbolic" &&
+ test_when_finished cleanup_git &&
+ (
+ P4CLIENT=client-sym &&
+ p4 client -i <<-EOF &&
+ Client: $P4CLIENT
+ Description: $P4CLIENT
+ Root: $symbolic
+ LineEnd: unix
+ View: //depot/... //$P4CLIENT/...
+ EOF
+ git p4 clone --dest="$git" //depot &&
+ cd "$git" &&
+ test_commit file2 &&
+ git config git-p4.skipSubmitEdit true &&
+ git p4 submit
+ )
+'
+
test_expect_success 'kill p4d' '
kill_p4d
'
fi
}
-# Call test_commit with the arguments "<message> [<file> [<contents>]]"
+# Call test_commit with the arguments "<message> [<file> [<contents> [<tag>]]]"
#
# This will commit a file with the given contents and the given commit
-# message. It will also add a tag with <message> as name.
+# message, and tag the resulting commit with the given tag name.
#
-# Both <file> and <contents> default to <message>.
+# <file>, <contents>, and <tag> all default to <message>.
test_commit () {
notick= &&
test_tick
fi &&
git commit $signoff -m "$1" &&
- git tag "$1"
+ git tag "${4:-$1}"
}
# Call test_merge with the arguments "<message> <commit>", where <commit>
# When the tests are run as root, permission tests will report that
# things are writable when they shouldn't be.
test -w / || test_set_prereq SANITY
+
+GIT_UNZIP=${GIT_UNZIP:-unzip}
+test_lazy_prereq UNZIP '
+ "$GIT_UNZIP" -v
+ test $? -ne 127
+'
return;
for (;;) {
- int cmp, len;
+ int cmp = 0; /* assigned before used */
+ int len;
if (!fgets(buffer, sizeof(buffer), f)) {
fclose(f);
struct ref *refs;
connect_setup(transport, for_push, 0);
- get_remote_heads(data->fd[0], &refs,
+ get_remote_heads(data->fd[0], NULL, 0, &refs,
for_push ? REF_NORMAL : 0, &data->extra_have);
data->got_remote_heads = 1;
if (!data->got_remote_heads) {
connect_setup(transport, 0, 0);
- get_remote_heads(data->fd[0], &refs_tmp, 0, NULL);
+ get_remote_heads(data->fd[0], NULL, 0, &refs_tmp, 0, NULL);
data->got_remote_heads = 1;
}
struct ref *tmp_refs;
connect_setup(transport, 1, 0);
- get_remote_heads(data->fd[0], &tmp_refs, REF_NORMAL, NULL);
+ get_remote_heads(data->fd[0], NULL, 0, &tmp_refs, REF_NORMAL, NULL);
data->got_remote_heads = 1;
}
match_flags |= MATCH_REFS_MIRROR;
if (flags & TRANSPORT_PUSH_PRUNE)
match_flags |= MATCH_REFS_PRUNE;
+ if (flags & TRANSPORT_PUSH_FOLLOW_TAGS)
+ match_flags |= MATCH_REFS_FOLLOW_TAGS;
if (match_push_refs(local_refs, &remote_refs,
refspec_nr, refspec, match_flags)) {
#define TRANSPORT_PUSH_PRUNE 128
#define TRANSPORT_RECURSE_SUBMODULES_ON_DEMAND 256
#define TRANSPORT_PUSH_NO_HOOK 512
+#define TRANSPORT_PUSH_FOLLOW_TAGS 1024
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
* otherwise maximum packet size (up to 65520 bytes).
*/
static int use_sideband;
-static int debug_fd;
static int advertise_refs;
static int stateless_rpc;
alarm(timeout);
}
-static int strip(char *line, int len)
-{
- if (len && line[len-1] == '\n')
- line[--len] = 0;
- return len;
-}
-
static ssize_t send_client_data(int fd, const char *data, ssize_t sz)
{
if (use_sideband)
xwrite(fd, data, sz);
return sz;
}
- return safe_write(fd, data, sz);
+ write_or_die(fd, data, sz);
+ return sz;
}
static FILE *pack_pipe = NULL;
if (!has_sha1_file(sha1))
return -1;
- o = lookup_object(sha1);
- if (!(o && o->parsed))
- o = parse_object(sha1);
+ o = parse_object(sha1);
if (!o)
die("oops (%s)", sha1_to_hex(sha1));
if (o->type == OBJ_COMMIT) {
static int get_common_commits(void)
{
- static char line[1000];
unsigned char sha1[20];
char last_hex[41];
int got_common = 0;
save_commit_buffer = 0;
for (;;) {
- int len = packet_read_line(0, line, sizeof(line));
+ char *line = packet_read_line(0, NULL);
reset_timeout();
- if (!len) {
+ if (!line) {
if (multi_ack == 2 && got_common
&& !got_other && ok_to_give_up()) {
sent_ready = 1;
got_other = 0;
continue;
}
- strip(line, len);
if (!prefixcmp(line, "have ")) {
switch (got_sha1(line+5, sha1)) {
case -1: /* they have what we do not */
static void receive_needs(void)
{
struct object_array shallows = OBJECT_ARRAY_INIT;
- static char line[1000];
- int len, depth = 0;
+ int depth = 0;
int has_non_tip = 0;
shallow_nr = 0;
- if (debug_fd)
- write_str_in_full(debug_fd, "#S\n");
for (;;) {
struct object *o;
const char *features;
unsigned char sha1_buf[20];
- len = packet_read_line(0, line, sizeof(line));
+ char *line = packet_read_line(0, NULL);
reset_timeout();
- if (!len)
+ if (!line)
break;
- if (debug_fd)
- write_in_full(debug_fd, line, len);
if (!prefixcmp(line, "shallow ")) {
unsigned char sha1[20];
struct object *object;
- if (get_sha1(line + 8, sha1))
+ if (get_sha1_hex(line + 8, sha1))
die("invalid shallow line: %s", line);
object = parse_object(sha1);
if (!object)
die("did not find object for %s", line);
if (object->type != OBJ_COMMIT)
die("invalid shallow object %s", sha1_to_hex(sha1));
- object->flags |= CLIENT_SHALLOW;
- add_object_array(object, NULL, &shallows);
+ if (!(object->flags & CLIENT_SHALLOW)) {
+ object->flags |= CLIENT_SHALLOW;
+ add_object_array(object, NULL, &shallows);
+ }
continue;
}
if (!prefixcmp(line, "deepen ")) {
if (parse_feature_request(features, "include-tag"))
use_include_tag = 1;
- o = lookup_object(sha1_buf);
+ o = parse_object(sha1_buf);
if (!o)
die("git upload-pack: not our ref %s",
sha1_to_hex(sha1_buf));
add_object_array(o, NULL, &want_obj);
}
}
- if (debug_fd)
- write_str_in_full(debug_fd, "#E\n");
/*
* We have sent all our refs already, and the other end
if (is_repository_shallow())
die("attempt to fetch/clone from a shallow repository");
git_config(upload_pack_config, NULL);
- if (getenv("GIT_DEBUG_SEND_PACK"))
- debug_fd = atoi(getenv("GIT_DEBUG_SEND_PACK"));
upload_pack();
return 0;
}
return out;
}
#endif
+
+/*
+ * Returns first character length in bytes for multi-byte `text` according to
+ * `encoding`.
+ *
+ * - The `text` pointer is updated to point at the next character.
+ * - When `remainder_p` is not NULL, on entry `*remainder_p` is how much bytes
+ * we can consume from text, and on exit `*remainder_p` is reduced by returned
+ * character length. Otherwise `text` is treated as limited by NUL.
+ */
+int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding)
+{
+ int chrlen;
+ const char *p = *text;
+ size_t r = (remainder_p ? *remainder_p : SIZE_MAX);
+
+ if (r < 1)
+ return 0;
+
+ if (is_encoding_utf8(encoding)) {
+ pick_one_utf8_char(&p, &r);
+
+ chrlen = p ? (p - *text)
+ : 1 /* not valid UTF-8 -> raw byte sequence */;
+ }
+ else {
+ /*
+ * TODO use iconv to decode one char and obtain its chrlen
+ * for now, let's treat encodings != UTF-8 as one-byte
+ */
+ chrlen = 1;
+ }
+
+ *text += chrlen;
+ if (remainder_p)
+ *remainder_p -= chrlen;
+
+ return chrlen;
+}
#define reencode_string(a,b,c) NULL
#endif
+int mbs_chrlen(const char **text, size_t *remainder_p, const char *encoding);
+
#endif
#include "cache.h"
+static void check_pipe(int err)
+{
+ if (err == EPIPE) {
+ signal(SIGPIPE, SIG_DFL);
+ raise(SIGPIPE);
+ /* Should never happen, but just in case... */
+ exit(141);
+ }
+}
+
/*
* Some cases use stdio, but want to flush after the write
* to get error handling (and to get better interactive
return;
}
if (fflush(f)) {
- if (errno == EPIPE)
- exit(0);
+ check_pipe(errno);
die_errno("write failure on '%s'", desc);
}
}
void write_or_die(int fd, const void *buf, size_t count)
{
if (write_in_full(fd, buf, count) < 0) {
- if (errno == EPIPE)
- exit(0);
+ check_pipe(errno);
die_errno("write error");
}
}
int write_or_whine_pipe(int fd, const void *buf, size_t count, const char *msg)
{
if (write_in_full(fd, buf, count) < 0) {
- if (errno == EPIPE)
- exit(0);
+ check_pipe(errno);
fprintf(stderr, "%s: write error (%s)\n",
msg, strerror(errno));
return 0;
{
if (state->branch)
status_printf_ln(s, color,
- _("You are currently bisecting branch '%s'."),
+ _("You are currently bisecting, started from branch '%s'."),
state->branch);
else
status_printf_ln(s, color,
/*
* Extract branch information from rebase/bisect
*/
-static void read_and_strip_branch(struct strbuf *sb,
- const char **branch,
- const char *path)
+static char *read_and_strip_branch(const char *path)
{
+ struct strbuf sb = STRBUF_INIT;
unsigned char sha1[20];
- strbuf_reset(sb);
- if (strbuf_read_file(sb, git_path("%s", path), 0) <= 0)
- return;
+ if (strbuf_read_file(&sb, git_path("%s", path), 0) <= 0)
+ goto got_nothing;
- while (sb->len && sb->buf[sb->len - 1] == '\n')
- strbuf_setlen(sb, sb->len - 1);
- if (!sb->len)
- return;
- if (!prefixcmp(sb->buf, "refs/heads/"))
- *branch = sb->buf + strlen("refs/heads/");
- else if (!prefixcmp(sb->buf, "refs/"))
- *branch = sb->buf;
- else if (!get_sha1_hex(sb->buf, sha1)) {
+ while (&sb.len && sb.buf[sb.len - 1] == '\n')
+ strbuf_setlen(&sb, sb.len - 1);
+ if (!sb.len)
+ goto got_nothing;
+ if (!prefixcmp(sb.buf, "refs/heads/"))
+ strbuf_remove(&sb,0, strlen("refs/heads/"));
+ else if (!prefixcmp(sb.buf, "refs/"))
+ ;
+ else if (!get_sha1_hex(sb.buf, sha1)) {
const char *abbrev;
abbrev = find_unique_abbrev(sha1, DEFAULT_ABBREV);
- strbuf_reset(sb);
- strbuf_addstr(sb, abbrev);
- *branch = sb->buf;
- } else if (!strcmp(sb->buf, "detached HEAD")) /* rebase */
- ;
+ strbuf_reset(&sb);
+ strbuf_addstr(&sb, abbrev);
+ } else if (!strcmp(sb.buf, "detached HEAD")) /* rebase */
+ goto got_nothing;
else /* bisect */
- *branch = sb->buf;
+ ;
+ return strbuf_detach(&sb, NULL);
+
+got_nothing:
+ strbuf_release(&sb);
+ return NULL;
}
-static void wt_status_print_state(struct wt_status *s)
+struct grab_1st_switch_cbdata {
+ int found;
+ struct strbuf buf;
+ unsigned char nsha1[20];
+};
+
+static int grab_1st_switch(unsigned char *osha1, unsigned char *nsha1,
+ const char *email, unsigned long timestamp, int tz,
+ const char *message, void *cb_data)
{
- const char *state_color = color(WT_STATUS_HEADER, s);
- struct strbuf branch = STRBUF_INIT;
- struct strbuf onto = STRBUF_INIT;
- struct wt_status_state state;
- struct stat st;
+ struct grab_1st_switch_cbdata *cb = cb_data;
+ const char *target = NULL, *end;
- memset(&state, 0, sizeof(state));
+ if (prefixcmp(message, "checkout: moving from "))
+ return 0;
+ message += strlen("checkout: moving from ");
+ target = strstr(message, " to ");
+ if (!target)
+ return 0;
+ target += strlen(" to ");
+ strbuf_reset(&cb->buf);
+ hashcpy(cb->nsha1, nsha1);
+ for (end = target; *end && *end != '\n'; end++)
+ ;
+ strbuf_add(&cb->buf, target, end - target);
+ cb->found = 1;
+ return 1;
+}
+
+static void wt_status_get_detached_from(struct wt_status_state *state)
+{
+ struct grab_1st_switch_cbdata cb;
+ struct commit *commit;
+ unsigned char sha1[20];
+ char *ref = NULL;
+
+ strbuf_init(&cb.buf, 0);
+ if (for_each_reflog_ent_reverse("HEAD", grab_1st_switch, &cb) <= 0) {
+ strbuf_release(&cb.buf);
+ return;
+ }
+
+ if (dwim_ref(cb.buf.buf, cb.buf.len, sha1, &ref) == 1 &&
+ /* sha1 is a commit? match without further lookup */
+ (!hashcmp(cb.nsha1, sha1) ||
+ /* perhaps sha1 is a tag, try to dereference to a commit */
+ ((commit = lookup_commit_reference_gently(sha1, 1)) != NULL &&
+ !hashcmp(cb.nsha1, commit->object.sha1)))) {
+ int ofs;
+ if (!prefixcmp(ref, "refs/tags/"))
+ ofs = strlen("refs/tags/");
+ else if (!prefixcmp(ref, "refs/remotes/"))
+ ofs = strlen("refs/remotes/");
+ else
+ ofs = 0;
+ state->detached_from = xstrdup(ref + ofs);
+ } else
+ state->detached_from =
+ xstrdup(find_unique_abbrev(cb.nsha1, DEFAULT_ABBREV));
+ hashcpy(state->detached_sha1, cb.nsha1);
+
+ free(ref);
+ strbuf_release(&cb.buf);
+}
+
+void wt_status_get_state(struct wt_status_state *state,
+ int get_detached_from)
+{
+ struct stat st;
if (!stat(git_path("MERGE_HEAD"), &st)) {
- state.merge_in_progress = 1;
+ state->merge_in_progress = 1;
} else if (!stat(git_path("rebase-apply"), &st)) {
if (!stat(git_path("rebase-apply/applying"), &st)) {
- state.am_in_progress = 1;
+ state->am_in_progress = 1;
if (!stat(git_path("rebase-apply/patch"), &st) && !st.st_size)
- state.am_empty_patch = 1;
+ state->am_empty_patch = 1;
} else {
- state.rebase_in_progress = 1;
- read_and_strip_branch(&branch, &state.branch,
- "rebase-apply/head-name");
- read_and_strip_branch(&onto, &state.onto,
- "rebase-apply/onto");
+ state->rebase_in_progress = 1;
+ state->branch = read_and_strip_branch("rebase-apply/head-name");
+ state->onto = read_and_strip_branch("rebase-apply/onto");
}
} else if (!stat(git_path("rebase-merge"), &st)) {
if (!stat(git_path("rebase-merge/interactive"), &st))
- state.rebase_interactive_in_progress = 1;
+ state->rebase_interactive_in_progress = 1;
else
- state.rebase_in_progress = 1;
- read_and_strip_branch(&branch, &state.branch,
- "rebase-merge/head-name");
- read_and_strip_branch(&onto, &state.onto,
- "rebase-merge/onto");
+ state->rebase_in_progress = 1;
+ state->branch = read_and_strip_branch("rebase-merge/head-name");
+ state->onto = read_and_strip_branch("rebase-merge/onto");
} else if (!stat(git_path("CHERRY_PICK_HEAD"), &st)) {
- state.cherry_pick_in_progress = 1;
+ state->cherry_pick_in_progress = 1;
}
if (!stat(git_path("BISECT_LOG"), &st)) {
- state.bisect_in_progress = 1;
- read_and_strip_branch(&branch, &state.branch,
- "BISECT_START");
+ state->bisect_in_progress = 1;
+ state->branch = read_and_strip_branch("BISECT_START");
}
- if (state.merge_in_progress)
- show_merge_in_progress(s, &state, state_color);
- else if (state.am_in_progress)
- show_am_in_progress(s, &state, state_color);
- else if (state.rebase_in_progress || state.rebase_interactive_in_progress)
- show_rebase_in_progress(s, &state, state_color);
- else if (state.cherry_pick_in_progress)
- show_cherry_pick_in_progress(s, &state, state_color);
- if (state.bisect_in_progress)
- show_bisect_in_progress(s, &state, state_color);
- strbuf_release(&branch);
- strbuf_release(&onto);
+ if (get_detached_from)
+ wt_status_get_detached_from(state);
+}
+
+static void wt_status_print_state(struct wt_status *s,
+ struct wt_status_state *state)
+{
+ const char *state_color = color(WT_STATUS_HEADER, s);
+ if (state->merge_in_progress)
+ show_merge_in_progress(s, state, state_color);
+ else if (state->am_in_progress)
+ show_am_in_progress(s, state, state_color);
+ else if (state->rebase_in_progress || state->rebase_interactive_in_progress)
+ show_rebase_in_progress(s, state, state_color);
+ else if (state->cherry_pick_in_progress)
+ show_cherry_pick_in_progress(s, state, state_color);
+ if (state->bisect_in_progress)
+ show_bisect_in_progress(s, state, state_color);
}
void wt_status_print(struct wt_status *s)
{
const char *branch_color = color(WT_STATUS_ONBRANCH, s);
const char *branch_status_color = color(WT_STATUS_HEADER, s);
+ struct wt_status_state state;
+
+ memset(&state, 0, sizeof(state));
+ wt_status_get_state(&state,
+ s->branch && !strcmp(s->branch, "HEAD"));
if (s->branch) {
const char *on_what = _("On branch ");
if (!prefixcmp(branch_name, "refs/heads/"))
branch_name += 11;
else if (!strcmp(branch_name, "HEAD")) {
- branch_name = "";
branch_status_color = color(WT_STATUS_NOBRANCH, s);
- on_what = _("Not currently on any branch.");
+ if (state.detached_from) {
+ unsigned char sha1[20];
+ branch_name = state.detached_from;
+ if (!get_sha1("HEAD", sha1) &&
+ !hashcmp(sha1, state.detached_sha1))
+ on_what = _("HEAD detached at ");
+ else
+ on_what = _("HEAD detached from ");
+ } else {
+ branch_name = "";
+ on_what = _("Not currently on any branch.");
+ }
}
status_printf(s, color(WT_STATUS_HEADER, s), "");
status_printf_more(s, branch_status_color, "%s", on_what);
wt_status_print_tracking(s);
}
- wt_status_print_state(s);
+ wt_status_print_state(s, &state);
+ free(state.branch);
+ free(state.onto);
+ free(state.detached_from);
+
if (s->is_initial) {
status_printf_ln(s, color(WT_STATUS_HEADER, s), "");
status_printf_ln(s, color(WT_STATUS_HEADER, s), _("Initial commit"));
int rebase_interactive_in_progress;
int cherry_pick_in_progress;
int bisect_in_progress;
- const char *branch;
- const char *onto;
+ char *branch;
+ char *onto;
+ char *detached_from;
+ unsigned char detached_sha1[20];
};
void wt_status_prepare(struct wt_status *s);
void wt_status_print(struct wt_status *s);
void wt_status_collect(struct wt_status *s);
+void wt_status_get_state(struct wt_status_state *state, int get_detached_from);
void wt_shortstatus_print(struct wt_status *s);
void wt_porcelain_print(struct wt_status *s);
strm->z.msg ? strm->z.msg : "no message");
}
-void git_deflate_init_gzip(git_zstream *strm, int level)
+static void do_git_deflate_init(git_zstream *strm, int level, int windowBits)
{
- /*
- * Use default 15 bits, +16 is to generate gzip header/trailer
- * instead of the zlib wrapper.
- */
- const int windowBits = 15 + 16;
int status;
zlib_pre_call(strm);
strm->z.msg ? strm->z.msg : "no message");
}
+void git_deflate_init_gzip(git_zstream *strm, int level)
+{
+ /*
+ * Use default 15 bits, +16 is to generate gzip header/trailer
+ * instead of the zlib wrapper.
+ */
+ return do_git_deflate_init(strm, level, 15 + 16);
+}
+
+void git_deflate_init_raw(git_zstream *strm, int level)
+{
+ /*
+ * Use default 15 bits, negate the value to get raw compressed
+ * data without zlib header and trailer.
+ */
+ return do_git_deflate_init(strm, level, -15);
+}
+
int git_deflate_abort(git_zstream *strm)
{
int status;