Fixes since v1.6.0
------------------
+* "git diff --cc" did not honor content mangling specified by
+ gitattributes and core.autocrlf when reading from the work tree.
+
* "git diff --check" incorrectly detected new trailing blank lines when
whitespace check was in effect.
* "git format-patch" peeked before the beginning of a string when
"format.headers" variable is empty (a misconfiguration).
+* "git help help" did not work correctly.
+
* "git mailinfo" (hence "git am") was unhappy when MIME multipart message
contained garbage after the finishing boundary.
* "git merge" did not refresh the index correctly when a merge resulted in
a fast-forward.
-Contains other various documentation fixes.
+* "git merge" did not resolve a truly trivial merges that can be done
+ without content level merges.
---
-exec >/var/tmp/1
-O=v1.6.0-14-g3a634dc
-echo O=$(git describe maint)
-git shortlog --no-merges $O..maint
+* "git svn dcommit" to a repository with URL that has embedded usernames
+ did not work correctly.
+
+Contains other various documentation fixes.
`EDITOR` environment variables and then finally `vi`.
core.pager::
- The command that git will use to paginate output. Can be overridden
- with the `GIT_PAGER` environment variable.
+ The command that git will use to paginate output. Can
+ be overridden with the `GIT_PAGER` environment
+ variable. Note that git sets the `LESS` environment
+ variable to `FRSX` if it is unset when it runs the
+ pager. One can change these settings by setting the
+ `LESS` variable to some other value or by giving the
+ `core.pager` option a value such as "`less -+FRSX`".
core.whitespace::
A comma separated list of common whitespace problems to
linkgit:git-repack[1].
pager.<cmd>::
- Allows to set your own pager preferences for each command, overriding
- the default. If `\--pager` or `\--no-pager` is specified on the command
- line, it takes precedence over this option.
+ Allows turning on or off pagination of the output of a
+ particular git subcommand when writing to a tty. If
+ `\--paginate` or `\--no-pager` is specified on the command line,
+ it takes precedence over this option. To disable pagination for
+ all commands, set `core.pager` or 'GIT_PAGER' to "`cat`".
pull.octopus::
The default merge strategy to use when pulling multiple branches
'GIT_PAGER'::
This environment variable overrides `$PAGER`. If it is set
to an empty string or to the value "cat", git will not launch
- a pager.
+ a pager. See also the `core.pager` option in
+ linkgit:git-config[1].
'GIT_SSH'::
If this environment variable is set then 'git-fetch'
the history between two branches (i.e. the HEAD and the MERGE_HEAD)
that modify the conflicted files.
+--argscmd=<command>::
+ Command to be run each time gitk has to determine the list of
+ <revs> to show. The command is expected to print on its standard
+ output a list of additional revs to be shown, one per line.
+ Use this instead of explicitly specifying <revs> if the set of
+ commits to show may vary between refreshes.
+
<revs>::
Limit the revisions to show. This can be either a single revision
------------------------------------------------
alice$ git fetch /home/bob/myrepo master
-alice$ git log -p ..FETCH_HEAD
+alice$ git log -p HEAD..FETCH_HEAD
------------------------------------------------
This operation is safe even if Alice has uncommitted local changes.
+The range notation HEAD..FETCH_HEAD" means "show everything that is reachable
+from the FETCH_HEAD but exclude anything that is reachable from HEAD.
+Alice already knows everything that leads to her current state (HEAD),
+and reviewing what Bob has in his state (FETCH_HEAD) that she has not
+seen with this command
+
+If Alice wants to visualize what Bob did since their histories forked
+she can issue the following command:
+
+------------------------------------------------
+$ gitk HEAD..FETCH_HEAD
+------------------------------------------------
+
+This uses the same two-dot range notation we saw earlier with 'git log'.
+
+Alice may want to view what both of them did since they forked.
+She can use three-dot form instead of the two-dot form:
+
+------------------------------------------------
+$ gitk HEAD...FETCH_HEAD
+------------------------------------------------
+
+This means "show everything that is reachable from either one, but
+exclude anything that is reachable from both of them".
+
+Please note that these range notation can be used with both gitk
+and "git log".
After inspecting what Bob did, if there is nothing urgent, Alice may
decide to continue working without pulling from Bob. If Bob's history
- '%an': author name
- '%aN': author name (respecting .mailmap)
- '%ae': author email
-- '%ad': author date
+- '%ad': author date (format respects --date= option)
- '%aD': author date, RFC2822 style
- '%ar': author date, relative
- '%at': author date, UNIX timestamp
NO_UNSETENV = YesPlease
NO_HSTRERROR = YesPlease
NO_SYS_SELECT_H = YesPlease
+ SNPRINTF_RETURNS_BOGUS = YesPlease
endif
ifneq (,$(findstring MINGW,$(uname_S)))
NO_MMAP = YesPlease
strbuf_add(&fmt, b + 8, c - b - 8);
strbuf_add(buf, src, b - src);
- format_commit_message(commit, fmt.buf, buf);
+ format_commit_message(commit, fmt.buf, buf, DATE_NORMAL);
len -= c + 1 - src;
src = c + 1;
}
if (!log_tree_commit(&rev, commit)) {
struct strbuf buf = STRBUF_INIT;
- format_commit_message(commit, "%h: %s", &buf);
+ format_commit_message(commit, "%h: %s", &buf, DATE_NORMAL);
printf("%s\n", buf.buf);
strbuf_release(&buf);
}
for (i = 0; i < idnums.size; i++) {
if (deco->base && deco->base->type == 1) {
mark = ptr_to_mark(deco->decoration);
- fprintf(f, ":%u %s\n", mark, sha1_to_hex(deco->base->sha1));
+ fprintf(f, ":%"PRIu32" %s\n", mark,
+ sha1_to_hex(deco->base->sha1));
}
deco++;
}
if ((plen <= namelen) &&
!strncmp(refname, p, plen) &&
(refname[plen] == '\0' ||
- refname[plen] == '/'))
+ refname[plen] == '/' ||
+ p[plen-1] == '/'))
break;
if (!fnmatch(p, refname, FNM_PATHNAME))
break;
struct dir_struct dir;
struct lock_file *lock_file = xcalloc(1, sizeof(struct lock_file));
- if (read_cache_unmerged())
- die("you need to resolve your current index first");
refresh_cache(REFRESH_QUIET);
fd = hold_locked_index(lock_file, 1);
static int merge_trivial(void)
{
unsigned char result_tree[20], result_commit[20];
- struct commit_list parent;
+ struct commit_list *parent = xmalloc(sizeof(struct commit_list *));
write_tree_trivial(result_tree);
printf("Wonderful.\n");
- parent.item = remoteheads->item;
- parent.next = NULL;
- commit_tree(merge_msg.buf, result_tree, &parent, result_commit);
+ parent->item = lookup_commit(head);
+ parent->next = xmalloc(sizeof(struct commit_list *));
+ parent->next->item = remoteheads->item;
+ parent->next->next = NULL;
+ commit_tree(merge_msg.buf, result_tree, parent, result_commit);
finish(result_commit, "In-index merge");
drop_save();
return 0;
int cnt = 0;
struct rev_info rev;
+ discard_cache();
if (read_cache() < 0)
die("failed to read the cache");
struct commit_list **remotes = &remoteheads;
setup_work_tree();
- if (unmerged_cache())
+ if (read_cache_unmerged())
die("You are in the middle of a conflicted merge.");
/*
}
/* Automerge succeeded. */
+ discard_cache();
write_tree_trivial(result_tree);
automerge_was_ok = 1;
break;
} else {
int fd = sha1close(f, NULL, 0);
fixup_pack_header_footer(fd, sha1, pack_tmp_name, nr_written);
- fsync_or_die(fd, pack_tmp_name);
close(fd);
}
revs.diff)
usage(rev_list_usage);
- save_commit_buffer = revs.verbose_header || revs.grep_filter;
+ save_commit_buffer = revs.verbose_header ||
+ revs.grep_filter.pattern_list;
if (bisect_list)
revs.limited = 1;
struct cache_tree *cache_tree;
time_t timestamp;
void *alloc;
- unsigned name_hash_initialized : 1;
+ unsigned name_hash_initialized : 1,
+ initialized : 1;
struct hash_table name_hash;
};
die("early EOF '%s'", elem->path);
result[len] = 0;
+
+ /* If not a fake symlink, apply filters, e.g. autocrlf */
+ if (is_file) {
+ struct strbuf buf;
+
+ strbuf_init(&buf, 0);
+ if (convert_to_git(elem->path, result, len, &buf, safe_crlf)) {
+ free(result);
+ result = strbuf_detach(&buf, &len);
+ result_size = len;
+ }
+ }
}
else {
deleted_file:
struct rev_info; /* in revision.h, it circularly uses enum cmit_fmt */
extern void get_commit_format(const char *arg, struct rev_info *);
extern void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb);
+ const void *format, struct strbuf *sb,
+ enum date_mode dmode);
extern void pretty_print_commit(enum cmit_fmt fmt, const struct commit*,
struct strbuf *,
int abbrev, const char *subject,
__gitcomp "--cached --stat --numstat --shortstat --summary
--patch-with-stat --name-only --name-status --color
--no-color --color-words --no-renames --check
- --full-index --binary --abbrev --diff-filter
+ --full-index --binary --abbrev --diff-filter=
--find-copies-harder --pickaxe-all --pickaxe-regex
--text --ignore-space-at-eol --ignore-space-change
--ignore-all-space --exit-code --quiet --ext-diff
def p4_write_pipe(c, str):
real_cmd = p4_build_cmd(c)
- return write_pipe(c, str)
+ return write_pipe(real_cmd, str)
def read_pipe(c, ignore_error=False):
if verbose:
Usage
=====
-git-p4 supports two main modes: Importing from Perforce to a Git repository is
-done using "git-p4 sync" or "git-p4 rebase". Submitting changes from Git back
-to Perforce is done using "git-p4 submit".
+git-p4 can be used in two different ways:
+
+1) To import changes from Perforce to a Git repository, using "git-p4 sync".
+
+2) To submit changes from Git back to Perforce, using "git-p4 submit".
Importing
=========
-You can simply start with
+Simply start with
git-p4 clone //depot/path/project
git-p4 clone //depot/path/project myproject
-This will create an empty git repository in a subdirectory called "project" (or
-"myproject" with the second command), import the head revision from the
-specified perforce path into a git "p4" branch (remotes/p4 actually), create a
-master branch off it and check it out. If you want the entire history (not just
-the head revision) then you can simply append a "@all" to the depot path:
+This will:
+
+1) Create an empty git repository in a subdirectory called "project" (or
+"myproject" with the second command)
+
+2) Import the head revision from the given Perforce path into a git branch
+called "p4" (remotes/p4 actually)
+
+3) Create a master branch based on it and check it out.
+
+If you want the entire history (not just the head revision) then you can simply
+append a "@all" to the depot path:
git-p4 clone //depot/project/main@all myproject
This will import the current head revision of the specified depot path into a
"remotes/p4/master" branch of your git repository. You can use the
---branch=mybranch option to use a different branch.
+--branch=mybranch option to import into a different branch.
-If you want to import the entire history of a given depot path just use
+If you want to import the entire history of a given depot path simply use:
git-p4 sync //path/in/depot@all
+
+Note:
+
To achieve optimal compression you may want to run 'git repack -a -d -f' after
a big import. This may take a while.
-Support for Perforce integrations is still work in progress. Don't bother
-trying it unless you want to hack on it :)
-
Incremental Imports
===================
-After an initial import you can easily synchronize your git repository with
-newer changes from the Perforce depot by just calling
+After an initial import you can continue to synchronize your git repository
+with newer changes from the Perforce depot by just calling
git-p4 sync
in your git repository. By default the "remotes/p4/master" branch is updated.
-It is recommended to run 'git repack -a -d -f' from time to time when using
-incremental imports to optimally combine the individual git packs that each
-incremental import creates through the use of git-fast-import.
+Advanced Setup
+==============
+
+Suppose you have a periodically updated git repository somewhere, containing a
+complete import of a Perforce project. This repository can be cloned and used
+with git-p4. When updating the cloned repository with the "sync" command,
+git-p4 will try to fetch changes from the original repository first. The git
+protocol used with this is usually faster than importing from Perforce
+directly.
+
+This behaviour can be disabled by setting the "git-p4.syncFromOrigin" git
+configuration variable to "false".
Updating
========
==========
git-p4 has support for submitting changes from a git repository back to the
-Perforce depot. This requires a Perforce checkout separate to your git
+Perforce depot. This requires a Perforce checkout separate from your git
repository. To submit all changes that are in the current git branch but not in
the "p4" branch (or "origin" if "p4" doesn't exist) simply call
git-p4 submit --continue
-After submitting you should sync your perforce import branch ("p4" or "origin")
-from Perforce using git-p4's sync command.
-
-If you have changes in your working directory that you haven't committed into
-git yet but that you want to commit to Perforce directly ("quick fixes") then
-you do not have to go through the intermediate step of creating a git commit
-first but you can just call
-
- git-p4 submit --direct
-
-
Example
=======
*/
#include "cache.h"
+/* Just so that no insane platform contaminate namespace with these symbols */
+#undef SS
+#undef AA
+#undef DD
+
#define SS GIT_SPACE
#define AA GIT_ALPHA
#define DD GIT_DIGIT
return this_dir;
}
+static int dirstat_compare(const void *_a, const void *_b)
+{
+ const struct dirstat_file *a = _a;
+ const struct dirstat_file *b = _b;
+ return strcmp(a->name, b->name);
+}
+
static void show_dirstat(struct diff_options *options)
{
int i;
return;
/* Show all directories with more than x% of the changes */
+ qsort(dir.files, dir.nr, sizeof(dir.files[0]), dirstat_compare);
gather_dirstat(options->file, &dir, changed, "", 0);
}
{
load_command_list();
return is_in_cmdlist(&main_cmds, s) ||
- is_in_cmdlist(&other_cmds, s);
+ is_in_cmdlist(&other_cmds, s) ||
+ !strcmp(s, "help");
}
static const char *prepend(const char *prefix, const char *cmd)
char *index_name_buf = NULL, *keep_name_buf = NULL;
struct pack_idx_entry **idx_objects;
unsigned char sha1[20];
+ int nongit = 0;
+ setup_git_directory_gently(&nongit);
git_config(git_index_pack_config, NULL);
for (i = 1; i < argc; i++) {
SHA1_Final(pack_file_sha1, &c);
write_or_die(pack_fd, pack_file_sha1, 20);
+ fsync_or_die(pack_fd, pack_name);
}
char *index_pack_lockfile(int ip_out)
}
static size_t format_person_part(struct strbuf *sb, char part,
- const char *msg, int len)
+ const char *msg, int len, enum date_mode dmode)
{
/* currently all placeholders have same length */
const int placeholder_len = 2;
switch (part) {
case 'd': /* date */
- strbuf_addstr(sb, show_date(date, tz, DATE_NORMAL));
+ strbuf_addstr(sb, show_date(date, tz, dmode));
return placeholder_len;
case 'D': /* date, RFC2822 style */
strbuf_addstr(sb, show_date(date, tz, DATE_RFC2822));
struct format_commit_context {
const struct commit *commit;
+ enum date_mode dmode;
/* These offsets are relative to the start of the commit message. */
int commit_header_parsed;
return 1;
case 'a': /* author ... */
return format_person_part(sb, placeholder[1],
- msg + c->author.off, c->author.len);
+ msg + c->author.off, c->author.len,
+ c->dmode);
case 'c': /* committer ... */
return format_person_part(sb, placeholder[1],
- msg + c->committer.off, c->committer.len);
+ msg + c->committer.off, c->committer.len,
+ c->dmode);
case 'e': /* encoding */
strbuf_add(sb, msg + c->encoding.off, c->encoding.len);
return 1;
}
void format_commit_message(const struct commit *commit,
- const void *format, struct strbuf *sb)
+ const void *format, struct strbuf *sb,
+ enum date_mode dmode)
{
struct format_commit_context context;
memset(&context, 0, sizeof(context));
context.commit = commit;
+ context.dmode = dmode;
strbuf_expand(sb, format, format_commit_item, &context);
}
const char *encoding;
if (fmt == CMIT_FMT_USERFORMAT) {
- format_commit_message(commit, user_format, sb);
+ format_commit_message(commit, user_format, sb, dmode);
return;
}
size_t mmap_size;
errno = EBUSY;
- if (istate->alloc)
+ if (istate->initialized)
return istate->cache_nr;
errno = ENOENT;
* index size
*/
istate->alloc = xmalloc(estimate_cache_size(mmap_size, istate->cache_nr));
+ istate->initialized = 1;
src_offset = sizeof(*hdr);
dst_offset = 0;
cache_tree_free(&(istate->cache_tree));
free(istate->alloc);
istate->alloc = NULL;
+ istate->initialized = 0;
/* no need to throw away allocated active_cache */
return 0;
revs->commit_format = CMIT_FMT_DEFAULT;
+ revs->grep_filter.status_only = 1;
+ revs->grep_filter.pattern_tail = &(revs->grep_filter.pattern_list);
+ revs->grep_filter.regflags = REG_NEWLINE;
+
diff_setup(&revs->diffopt);
if (prefix && !revs->diffopt.prefix) {
revs->diffopt.prefix = prefix;
static void add_grep(struct rev_info *revs, const char *ptn, enum grep_pat_token what)
{
- if (!revs->grep_filter) {
- struct grep_opt *opt = xcalloc(1, sizeof(*opt));
- opt->status_only = 1;
- opt->pattern_tail = &(opt->pattern_list);
- opt->regflags = REG_NEWLINE;
- revs->grep_filter = opt;
- }
- append_grep_pattern(revs->grep_filter, ptn,
- "command line", 0, what);
+ append_grep_pattern(&revs->grep_filter, ptn, "command line", 0, what);
}
static void add_header_grep(struct rev_info *revs, const char *field, const char *pattern)
} else if (!prefixcmp(arg, "--grep=")) {
add_message_grep(revs, arg+7);
} else if (!strcmp(arg, "--extended-regexp") || !strcmp(arg, "-E")) {
- if (revs->grep_filter)
- revs->grep_filter->regflags |= REG_EXTENDED;
+ revs->grep_filter.regflags |= REG_EXTENDED;
} else if (!strcmp(arg, "--regexp-ignore-case") || !strcmp(arg, "-i")) {
- if (revs->grep_filter)
- revs->grep_filter->regflags |= REG_ICASE;
+ revs->grep_filter.regflags |= REG_ICASE;
} else if (!strcmp(arg, "--fixed-strings") || !strcmp(arg, "-F")) {
- if (revs->grep_filter)
- revs->grep_filter->fixed = 1;
+ revs->grep_filter.fixed = 1;
} else if (!strcmp(arg, "--all-match")) {
- if (revs->grep_filter)
- revs->grep_filter->all_match = 1;
+ revs->grep_filter.all_match = 1;
} else if (!prefixcmp(arg, "--encoding=")) {
arg += 11;
if (strcmp(arg, "none"))
if (diff_setup_done(&revs->diffopt) < 0)
die("diff_setup_done failed");
- if (revs->grep_filter) {
- compile_grep_patterns(revs->grep_filter);
- }
+ compile_grep_patterns(&revs->grep_filter);
if (revs->reverse && revs->reflog_info)
die("cannot combine --reverse with --walk-reflogs");
static int commit_match(struct commit *commit, struct rev_info *opt)
{
- if (!opt->grep_filter)
+ if (!opt->grep_filter.pattern_list)
return 1;
- return grep_buffer(opt->grep_filter,
+ return grep_buffer(&opt->grep_filter,
NULL, /* we say nothing, not even filename */
commit->buffer, strlen(commit->buffer));
}
#define REVISION_H
#include "parse-options.h"
+#include "grep.h"
#define SEEN (1u<<0)
#define UNINTERESTING (1u<<1)
int show_log_size;
/* Filter by commit log message */
- struct grep_opt *grep_filter;
+ struct grep_opt grep_filter;
/* Display history graph */
struct git_graph *graph;
{
char *prog;
struct commands *cmd;
+ int devnull_fd;
+
+ /*
+ * Always open file descriptors 0/1/2 to avoid clobbering files
+ * in die(). It also avoids not messing up when the pipes are
+ * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
+ */
+ devnull_fd = open("/dev/null", O_RDWR);
+ while (devnull_fd >= 0 && devnull_fd <= 2)
+ devnull_fd = dup(devnull_fd);
+ if (devnull_fd == -1)
+ die("opening /dev/null failed (%s)", strerror(errno));
+ close (devnull_fd);
/*
* Special hack to pretend to be a CVS server
'
+test_expect_success 'fail if the index has unresolved entries' '
+
+ rm -fr [abcd] &&
+ git checkout -f "$c1" &&
+
+ test_must_fail git merge "$c5" &&
+ test_must_fail git merge "$c5" 2> out &&
+ grep "You are in the middle of a conflicted merge" out
+
+'
+
test_expect_success 'merge-recursive remove conflict' '
rm -fr [abcd] &&
3. A quick brown fox jumps over the lazy cat, oops dog.
EOF
-cat >"$p1" "$p0"
+cat 2>/dev/null >"$p1" "$p0"
echo 'Foo Bar Baz' >"$p2"
test -f "$p1" && cmp "$p0" "$p1" || {
. ./test-lib.sh
-P1='pathname with HT'
-: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || {
- echo >&2 'Filesystem does not support HT in names'
- test_done
-}
-
FN='濱野'
GN='純'
HT=' '
'
DQ='"'
-echo foo > "Name and an${HT}HT"
+echo foo 2>/dev/null > "Name and an${HT}HT"
test -f "Name and an${HT}HT" || {
# since FAT/NTFS does not allow tabs in filenames, skip this test
say 'Your filesystem does not allow tabs in filenames, test skipped.'
git diff --check
'
+test_expect_success 'combined diff with autocrlf conversion' '
+
+ git reset --hard &&
+ echo >x hello &&
+ git commit -m "one side" x &&
+ git checkout HEAD^ &&
+ echo >x goodbye &&
+ git commit -m "the other side" x &&
+ git config core.autocrlf true &&
+ test_must_fail git merge master &&
+
+ git diff | sed -e "1,/^@@@/d" >actual &&
+ ! grep "^-" actual
+
+'
+
test_done
P2='pathname with SP'
P3='pathname
with LF'
-: >"$P1" 2>&1 && test -f "$P1" && rm -f "$P1" || {
- echo >&2 'Filesystem does not support tabs in names'
+: 2>/dev/null >"$P1" && test -f "$P1" && rm -f "$P1" || {
+ say 'Your filesystem does not allow tabs in filenames, test skipped.'
test_done
}
'
+test_expect_success 'setup case sensitivity tests' '
+ echo case >one &&
+ test_tick &&
+ git commit -a -m Second
+'
+
+test_expect_success 'log --grep' '
+ echo second >expect &&
+ git log -1 --pretty="tformat:%s" --grep=sec >actual &&
+ test_cmp expect actual
+'
+test_expect_success 'log -i --grep' '
+ echo Second >expect &&
+ git log -1 --pretty="tformat:%s" -i --grep=sec >actual &&
+ test_cmp expect actual
+'
+
+test_expect_success 'log --grep -i' '
+ echo Second >expect &&
+ git log -1 --pretty="tformat:%s" --grep=sec -i >actual &&
+ test_cmp expect actual
+'
test_done
commit 86c75cfd708a0e5868dc876ed5b8bb66c80b4873
EOF
+test_expect_success '%ad respects --date=' '
+ echo 2005-04-07 >expect.ad-short &&
+ git log -1 --date=short --pretty=tformat:%ad >output.ad-short master &&
+ test_cmp expect.ad-short output.ad-short
+'
+
test_expect_success 'empty email' '
test_tick &&
C=$(GIT_AUTHOR_EMAIL= git commit-tree HEAD^{tree} </dev/null) &&
test_debug 'gitk --all'
+test_expect_success 'in-index merge' '
+ git reset --hard c0 &&
+ git merge --no-ff -s resolve c1 > out &&
+ grep "Wonderful." out &&
+ verify_parents $c0 $c1
+'
+
+test_debug 'gitk --all'
+
test_done
git diff --exit-code &&
test -f c0.c &&
test -f c1.c &&
- test -f c2.c
+ test -f c2.c &&
+ test 3 = $(git ls-tree -r HEAD | wc -l) &&
+ test 3 = $(git ls-files | wc -l)
'
test_expect_success 'merge c2 to c3 (fails)' '
state.refresh_cache = 1;
memset(&o->result, 0, sizeof(o->result));
+ o->result.initialized = 1;
if (o->src_index)
o->result.timestamp = o->src_index->timestamp;
o->merge_size = len;