/test-match-trees
/test-parse-options
/test-path-utils
+/test-run-command
/test-sha1
/test-sigchain
/common-cmds.h
terminal. When more specific variables of color.* are set, they always
take precedence over this setting. Defaults to false.
-commit.status
+commit.status::
A boolean to enable/disable inclusion of status information in the
commit message template when using an editor to prepare the commit
message. Defaults to true.
Output
------
-show files just outputs the filename unless '--stage' is specified in
+'git ls-files' just outputs the filenames unless '--stage' is specified in
which case it outputs:
[<tag> ]<mode> <object> <stage> <file>
DESCRIPTION
-----------
-Merges the history specified by <commit> into HEAD, optionally using a
-specific merge strategy.
+Incorporates changes from the named commits (since the time their
+histories diverged from the current branch) into the current
+branch. This command is used by 'git pull' to incorporate changes
+from another repository and can be used by hand to merge changes
+from one branch into another.
+
+Assume the following history exists and the current branch is
+"`master`":
+
+------------
+ A---B---C topic
+ /
+ D---E---F---G master
+------------
+
+Then "`git merge topic`" will replay the changes made on the
+`topic` branch since it diverged from `master` (i.e., `E`) until
+its current commit (`C`) on top of `master`, and record the result
+in a new commit along with the names of the two parent commits and
+a log message from the user describing the changes.
+
+------------
+ A---B---C topic
+ / \
+ D---E---F---G---H master
+------------
The second syntax (<msg> `HEAD` <commit>...) is supported for
historical reasons. Do not use it from the command line or in
You need at least one <commit>. Specifying more than one
<commit> obviously means you are trying an Octopus.
-include::merge-strategies.txt[]
+PRE-MERGE CHECKS
+----------------
-If you tried a merge which resulted in complex conflicts and
-want to start over, you can recover with 'git reset'.
+Before applying outside changes, you should get your own work in
+good shape and committed locally, so it will not be clobbered if
+there are conflicts. See also linkgit:git-stash[1].
+'git pull' and 'git merge' will stop without doing anything when
+local uncommitted changes overlap with files that 'git pull'/'git
+merge' may need to update.
-CONFIGURATION
--------------
-include::merge-config.txt[]
+To avoid recording unrelated changes in the merge commit,
+'git pull' and 'git merge' will also abort if there are any changes
+registered in the index relative to the `HEAD` commit. (One
+exception is when the changed index entries are in the state that
+would result from the merge already.)
-branch.<name>.mergeoptions::
- Sets default options for merging into branch <name>. The syntax and
- supported options are the same as those of 'git merge', but option
- values containing whitespace characters are currently not supported.
+If all named commits are already ancestors of `HEAD`, 'git merge'
+will exit early with the message "Already up-to-date."
-HOW MERGE WORKS
----------------
-
-A merge is always between the current `HEAD` and one or more
-commits (usually, branch head or tag), and the index file must
-match the tree of `HEAD` commit (i.e. the contents of the last commit)
-when it starts out. In other words, `git diff --cached HEAD` must
-report no changes. (One exception is when the changed index
-entries are already in the same state that would result from
-the merge anyway.)
-
-Three kinds of merge can happen:
-
-* The merged commit is already contained in `HEAD`. This is the
- simplest case, called "Already up-to-date."
-
-* `HEAD` is already contained in the merged commit. This is the
- most common case especially when invoked from 'git pull':
- you are tracking an upstream repository, have committed no local
- changes and now you want to update to a newer upstream revision.
- Your `HEAD` (and the index) is updated to point at the merged
- commit, without creating an extra merge commit. This is
- called "Fast-forward".
-
-* Both the merged commit and `HEAD` are independent and must be
- tied together by a merge commit that has both of them as its parents.
- The rest of this section describes this "True merge" case.
-
-The chosen merge strategy merges the two commits into a single
-new source tree.
-When things merge cleanly, this is what happens:
-
-1. The results are updated both in the index file and in your
- working tree;
-2. Index file is written out as a tree;
-3. The tree gets committed; and
-4. The `HEAD` pointer gets advanced.
-
-Because of 2., we require that the original state of the index
-file matches exactly the current `HEAD` commit; otherwise we
-will write out your local changes already registered in your
-index file along with the merge result, which is not good.
-Because 1. involves only those paths differing between your
-branch and the branch you are merging
-(which is typically a fraction of the whole tree), you can
-have local modifications in your working tree as long as they do
-not overlap with what the merge updates.
-
-When there are conflicts, the following happens:
-
-1. `HEAD` stays the same.
-
-2. Cleanly merged paths are updated both in the index file and
- in your working tree.
+FAST-FORWARD MERGE
+------------------
+
+Often the current branch head is an ancestor of the named commit.
+This is the most common case especially when invoked from 'git
+pull': you are tracking an upstream repository, you have committed
+no local changes, and now you want to update to a newer upstream
+revision. In this case, a new commit is not needed to store the
+combined history; instead, the `HEAD` (along with the index) is
+updated to point at the named commit, without creating an extra
+merge commit.
+
+This behavior can be suppressed with the `--no-ff` option.
-3. For conflicting paths, the index file records up to three
- versions; stage1 stores the version from the common ancestor,
- stage2 from `HEAD`, and stage3 from the other branch (you
+TRUE MERGE
+----------
+
+Except in a fast-forward merge (see above), the branches to be
+merged must be tied together by a merge commit that has both of them
+as its parents.
+
+A merged version reconciling the changes from all branches to be
+merged is committed, and your `HEAD`, index, and working tree are
+updated to it. It is possible to have modifications in the working
+tree as long as they do not overlap; the update will preserve them.
+
+When it is not obvious how to reconcile the changes, the following
+happens:
+
+1. The `HEAD` pointer stays the same.
+2. The `MERGE_HEAD` ref is set to point to the other branch head.
+3. Paths that merged cleanly are updated both in the index file and
+ in your working tree.
+4. For conflicting paths, the index file records up to three
+ versions: stage 1 stores the version from the common ancestor,
+ stage 2 from `HEAD`, and stage 3 from `MERGE_HEAD` (you
can inspect the stages with `git ls-files -u`). The working
tree files contain the result of the "merge" program; i.e. 3-way
- merge results with familiar conflict markers `<<< === >>>`.
-
-4. No other changes are done. In particular, the local
+ merge results with familiar conflict markers `<<<` `===` `>>>`.
+5. No other changes are made. In particular, the local
modifications you had before you started merge will stay the
same and the index entries for them stay as they were,
i.e. matching `HEAD`.
+If you tried a merge which resulted in complex conflicts and
+want to start over, you can recover with `git reset --merge`.
+
HOW CONFLICTS ARE PRESENTED
---------------------------
mergetool which will work you through the merge.
* Look at the diffs. `git diff` will show a three-way diff,
- highlighting changes from both the HEAD and their versions.
+ highlighting changes from both the `HEAD` and `MERGE_HEAD`
+ versions.
- * Look at the diffs on their own. `git log --merge -p <path>`
- will show diffs first for the HEAD version and then
- their version.
+ * Look at the diffs from each branch. `git log --merge -p <path>`
+ will show diffs first for the `HEAD` version and then the
+ `MERGE_HEAD` version.
* Look at the originals. `git show :1:filename` shows the
- common ancestor, `git show :2:filename` shows the HEAD
- version and `git show :3:filename` shows their version.
+ common ancestor, `git show :2:filename` shows the `HEAD`
+ version, and `git show :3:filename` shows the `MERGE_HEAD`
+ version.
EXAMPLES
release/version name would be acceptable.
+include::merge-strategies.txt[]
+
+CONFIGURATION
+-------------
+include::merge-config.txt[]
+
+branch.<name>.mergeoptions::
+ Sets default options for merging into branch <name>. The syntax and
+ supported options are the same as those of 'git merge', but option
+ values containing whitespace characters are currently not supported.
+
SEE ALSO
--------
linkgit:git-fmt-merge-msg[1], linkgit:git-pull[1],
the name "origin" is used. For this latter case, this option
can be used to override the name "origin". In other words,
the difference between these two commands
-
--u::
---set-upstream::
- For every branch that is up to date or successfully pushed, add
- upstream (tracking) reference, used by argument-less
- linkgit:git-pull[1] and other commands. For more information,
- see 'branch.<name>.merge' in linkgit:git-config[1].
+
--------------------------
git push public #1
only if the current branch does not track a remote branch. This is
useful if you write an alias or script around 'git push'.
+-u::
+--set-upstream::
+ For every branch that is up to date or successfully pushed, add
+ upstream (tracking) reference, used by argument-less
+ linkgit:git-pull[1] and other commands. For more information,
+ see 'branch.<name>.merge' in linkgit:git-config[1].
+
--thin::
--no-thin::
These options are passed to 'git send-pack'. Thin
--------
[verse]
git log --pretty=short | 'git shortlog' [-h] [-n] [-s] [-e] [-w]
-git shortlog [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...]
+'git shortlog' [-n|--numbered] [-s|--summary] [-e|--email] [-w[<width>[,<indent1>[,<indent2>]]]] [<committish>...]
DESCRIPTION
-----------
COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DNOGDI -DHAVE_STRING_H -DHAVE_ALLOCA_H -Icompat -Icompat/fnmatch -Icompat/regex -Icompat/fnmatch -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -SUBSYSTEM:CONSOLE -NODEFAULTLIB:MSVCRT.lib
EXTLIBS = advapi32.lib shell32.lib wininet.lib ws2_32.lib
+ PTHREAD_LIBS =
lib =
ifndef DEBUG
BASIC_CFLAGS += -GL -Os -MT
NO_PYTHON = YesPlease
BLK_SHA1 = YesPlease
THREADED_DELTA_SEARCH = YesPlease
- COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch
+ COMPAT_CFLAGS += -D__USE_MINGW_ACCESS -DNOGDI -Icompat -Icompat/fnmatch -Icompat/win32
COMPAT_CFLAGS += -DSTRIP_EXTENSION=\".exe\"
COMPAT_OBJS += compat/mingw.o compat/fnmatch/fnmatch.o compat/winansi.o \
compat/win32/pthread.o
EXTLIBS += -lws2_32
+ PTHREAD_LIBS =
X = .exe
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
htmldir=doc/git/html/
#include "builtin.h"
#include "utf8.h"
-#define BLOCKING (1ul << 14)
-
/*
* FIXME! Share the code with "write-tree.c"
*/
return 0;
}
+static void debug_stage(const char *label, struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ printf("%s ", label);
+ if (!ce)
+ printf("(missing)\n");
+ else if (ce == o->df_conflict_entry)
+ printf("(conflict)\n");
+ else
+ printf("%06o #%d %s %.8s\n",
+ ce->ce_mode, ce_stage(ce), ce->name,
+ sha1_to_hex(ce->sha1));
+}
+
+static int debug_merge(struct cache_entry **stages, struct unpack_trees_options *o)
+{
+ int i;
+
+ printf("* %d-way merge\n", o->merge_size);
+ debug_stage("index", stages[0], o);
+ for (i = 1; i <= o->merge_size; i++) {
+ char buf[24];
+ sprintf(buf, "ent#%d", i);
+ debug_stage(buf, stages[i], o);
+ }
+ return 0;
+}
+
static struct lock_file lock_file;
int cmd_read_tree(int argc, const char **argv, const char *unused_prefix)
"don't check the working tree after merging", 1),
OPT_SET_INT(0, "no-sparse-checkout", &opts.skip_sparse_checkout,
"skip applying sparse checkout filter", 1),
+ OPT_SET_INT(0, "debug-unpack", &opts.debug_unpack,
+ "debug unpack-trees", 1),
OPT_END()
};
opts.head_idx = 1;
}
+ if (opts.debug_unpack)
+ opts.fn = debug_merge;
+
cache_tree_free(&active_cache_tree);
for (i = 0; i < nr_trees; i++) {
struct tree *tree = trees[i];
if (unpack_trees(nr_trees, t, &opts))
return 128;
+ if (opts.debug_unpack)
+ return 0; /* do not write the index out */
+
/*
* When reading only one tree (either the most basic form,
* "-m ent" or "--reset ent" form), we can obtain a fully
/* Only remove in work directory, not index */
#define CE_WT_REMOVE (0x400000)
+#define CE_UNPACKED (0x1000000)
+
/*
* Extended on-disk flags
*/
NULL
};
conn->env = env;
- *arg++ = "sh";
- *arg++ = "-c";
+ conn->use_shell = 1;
}
*arg++ = cmd.buf;
*arg = NULL;
show_modified(revs, tree, idx, 1, cached, match_missing);
}
-static inline void skip_same_name(struct cache_entry *ce, struct unpack_trees_options *o)
-{
- int len = ce_namelen(ce);
- const struct index_state *index = o->src_index;
-
- while (o->pos < index->cache_nr) {
- struct cache_entry *next = index->cache[o->pos];
- if (len != ce_namelen(next))
- break;
- if (memcmp(ce->name, next->name, len))
- break;
- o->pos++;
- }
-}
-
/*
* The unpack_trees() interface is designed for merging, so
* the different source entries are designed primarily for
struct cache_entry *tree = src[1];
struct rev_info *revs = o->unpack_data;
- if (idx && ce_stage(idx))
- skip_same_name(idx, o);
-
/*
* Unpack-trees generates a DF/conflict entry if
* there was a directory in the index and a tree
exit(128);
diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
+ diffcore_fix_diff_index(&revs->diffopt);
diffcore_std(&revs->diffopt);
diff_flush(&revs->diffopt);
return 0;
*q = outq;
}
+static int diffnamecmp(const void *a_, const void *b_)
+{
+ const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+ const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+ const char *name_a, *name_b;
+
+ name_a = a->one ? a->one->path : a->two->path;
+ name_b = b->one ? b->one->path : b->two->path;
+ return strcmp(name_a, name_b);
+}
+
+void diffcore_fix_diff_index(struct diff_options *options)
+{
+ struct diff_queue_struct *q = &diff_queued_diff;
+ qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+}
+
void diffcore_std(struct diff_options *options)
{
if (options->skip_stat_unmatch)
#define DIFF_PICKAXE_REGEX 2
extern void diffcore_std(struct diff_options *);
+extern void diffcore_fix_diff_index(struct diff_options *);
#define COMMON_DIFF_OPTIONS_HELP \
"\ncommon diff options:\n" \
# discarding the indented remainder of folded lines,
# and see if it looks like that they all begin with the
# header field names...
- sed -n -e '/^$/q' -e '/^[ ]/d' -e p "$1" |
+ tr -d '\015' <"$1" |
+ sed -n -e '/^$/q' -e '/^[ ]/d' -e p |
sane_egrep -v '^[!-9;-~]+:' >/dev/null ||
patch_format=mbox
fi
"$new_sha1" "$new_ref"
git cat-file tag "$ref" |
sed -n \
- -e "1,/^$/{
+ -e '1,/^$/{
/^object /d
/^type /d
/^tag /d
- }" \
+ }' \
-e '/^-----BEGIN PGP SIGNATURE-----/q' \
-e 'p' ) |
git mktag) ||
# TCL_PATH must be vaild for this to work.
#
-GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE
+GIT-VERSION-FILE: FORCE
@$(SHELL_PATH) ./GIT-VERSION-GEN
-include GIT-VERSION-FILE
GITGUI_MACOSXAPP=$(GITGUI_MACOSXAPP) \
#end TRACK_VARS
-GIT-GUI-VARS: .FORCE-GIT-GUI-VARS
+GIT-GUI-VARS: FORCE
@VARS='$(TRACK_VARS)'; \
if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \
echo 1>&2 " * new locations or Tcl/Tk interpreter"; \
endif
.PHONY: all install uninstall dist-version clean
-.PHONY: .FORCE-GIT-VERSION-FILE
-.PHONY: .FORCE-GIT-GUI-VARS
+.PHONY: FORCE
set _prefix {}
}]
&& [catch {
+ # beware that from the .git dir this sets _gitdir to .
+ # and _prefix to the empty string
set _gitdir [git rev-parse --git-dir]
set _prefix [git rev-parse --show-prefix]
} err]} {
choose_repository::pick
set picked 1
}
+
+# we expand the _gitdir when it's just a single dot (i.e. when we're being
+# run from the .git dir itself) lest the routines to find the worktree
+# get confused
+if {$_gitdir eq "."} {
+ set _gitdir [pwd]
+}
+
if {![file isdirectory $_gitdir] && [is_Cygwin]} {
catch {set _gitdir [exec cygpath --windows $_gitdir]}
}
} elseif {$s0 ne {_} && [string index $state 0] eq {_}
&& $head_info eq {}} {
set head_info $index_info
+ } elseif {$s0 eq {_} && [string index $state 0] ne {_}} {
+ set index_info $head_info
+ set head_info {}
}
set file_states($path) [list $s0$s1 $icon \
cd [file dirname [gitdir]]
set env(GIT_DIR) [file tail [gitdir]]
- eval exec $cmd $revs &
+ eval exec $cmd $revs "--" "--" &
if {$old_GIT_DIR eq {}} {
unset env(GIT_DIR)
[list .mbar.commit entryconf [.mbar.commit index last] -state]
.mbar.commit add command -label [mc "Unstage From Commit"] \
- -command do_unstage_selection
+ -command do_unstage_selection \
+ -accelerator $M1T-U
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
.mbar.commit add command -label [mc "Revert Changes"] \
- -command do_revert_selection
+ -command do_revert_selection \
+ -accelerator $M1T-J
lappend disable_on_lock \
[list .mbar.commit entryconf [.mbar.commit index last] -state]
bind $ui_comm <$M1B-Key-Return> {do_commit;break}
bind $ui_comm <$M1B-Key-t> {do_add_selection;break}
bind $ui_comm <$M1B-Key-T> {do_add_selection;break}
+bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break}
+bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break}
+bind $ui_comm <$M1B-Key-j> {do_revert_selection;break}
+bind $ui_comm <$M1B-Key-J> {do_revert_selection;break}
bind $ui_comm <$M1B-Key-i> {do_add_all;break}
bind $ui_comm <$M1B-Key-I> {do_add_all;break}
bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break}
foreach p [get_config gui.recentrepo] {
if {[_is_git [file join $p .git]]} {
lappend recent $p
+ } else {
+ _unset_recentrepo $p
}
}
return [lsort $recent]
proc _unset_recentrepo {p} {
regsub -all -- {([()\[\]{}\.^$+*?\\])} $p {\\\1} p
git config --global --unset gui.recentrepo "^$p\$"
+ load_config 1
}
proc _append_recentrepos {path} {
lappend recent $path
git config --global --add gui.recentrepo $path
+ load_config 1
while {[llength $recent] > 10} {
_unset_recentrepo [lindex $recent 0]
}
set i_l $next_l
}
+ set patch "$patch$pre_context"
set patch "@@ -$hln,$n +$hln,[eval expr $n $sign 1] @@\n$patch"
if {[catch {
toplevel $w
wm title $w [strcat "[appname] ([reponame]): " [mc "Index Error"]]
wm geometry $w "+[winfo rootx .]+[winfo rooty .]"
- pack [label $w.msg \
- -justify left \
- -anchor w \
- -text [strcat \
- [mc "Updating the Git index failed. A rescan will be automatically started to resynchronize git-gui."] \
- "\n\n$err"] \
- ] -anchor w
-
- frame $w.buttons
- button $w.buttons.continue \
+ set s [mc "Updating the Git index failed. A rescan will be automatically started to resynchronize git-gui."]
+ text $w.msg -yscrollcommand [list $w.vs set] \
+ -width [string length $s] -relief flat \
+ -borderwidth 0 -highlightthickness 0 \
+ -background [$w cget -background]
+ $w.msg tag configure bold -font font_uibold -justify center
+ scrollbar $w.vs -command [list $w.msg yview]
+ $w.msg insert end $s bold \n\n$err {}
+ $w.msg configure -state disabled
+
+ button $w.continue \
-text [mc "Continue"] \
-command [list destroy $w]
- pack $w.buttons.continue -side right -padx 5
- button $w.buttons.unlock \
+ button $w.unlock \
-text [mc "Unlock Index"] \
-command "destroy $w; _delete_indexlock"
- pack $w.buttons.unlock -side right
- pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+ grid $w.msg - $w.vs -sticky news
+ grid $w.unlock $w.continue - -sticky se -padx 2 -pady 2
+ grid columnconfigure $w 0 -weight 1
+ grid rowconfigure $w 0 -weight 1
wm protocol $w WM_DELETE_WINDOW update
- bind $w.buttons.continue <Visibility> "
+ bind $w.continue <Visibility> "
grab $w
- focus $w.buttons.continue
+ focus %W
"
tkwait window $w
else
# plain-old CGI
resolve_full_httpd
- list_mods=$(echo "$full_httpd" | sed "s/-f$/-l/")
+ list_mods=$(echo "$full_httpd" | sed 's/-f$/-l/')
$list_mods | sane_grep 'mod_cgi\.c' >/dev/null 2>&1 || \
if test -f "$module_path/mod_cgi.so"
then
*)
eval="git-merge $diffstat $no_commit $squash $no_ff $ff_only"
eval="$eval $log_arg $strategy_args $merge_args"
- eval="$eval \"$merge_name\" HEAD $merge_head $verbosity"
+ eval="$eval \"\$merge_name\" HEAD $merge_head $verbosity"
;;
esac
eval "exec $eval"
sed -e 1d -e '2,/^./{
/^$/d
}' <"$SQUASH_MSG".bak
- } >$SQUASH_MSG
+ } >"$SQUASH_MSG"
else
commit_message HEAD > "$FIXUP_MSG" || die "Cannot write $FIXUP_MSG"
COUNT=2
echo "# The first commit's message is:"
echo
cat "$FIXUP_MSG"
- } >$SQUASH_MSG
+ } >"$SQUASH_MSG"
fi
case $1 in
squash)
echo
commit_message $2 | sed -e 's/^/# /'
;;
- esac >>$SQUASH_MSG
+ esac >>"$SQUASH_MSG"
}
peek_next_command () {
- sed -n -e "/^#/d" -e "/^$/d" -e "s/ .*//p" -e "q" < "$TODO"
+ sed -n -e "/^#/d" -e '/^$/d' -e "s/ .*//p" -e "q" < "$TODO"
}
# A squash/fixup has failed. Prepare the long version of the squash
printf "Already applied: %0${prec}d " $msgnum
fi
fi
- if test -z "$GIT_QUIET"
- then
- git rev-list --pretty=oneline -1 "$cmt" | sed -e 's/^[^ ]* //'
- fi
+ test -z "$GIT_QUIET" &&
+ git log --format=%s -1 "$cmt"
prev_head=`git rev-parse HEAD^0`
# save the resulting commit so we can read-tree on it later
}
// the server returned error
- if (xhr.readyState === 3 && xhr.status !== 200) {
+ // try ... catch block is to work around bug in IE8
+ try {
+ if (xhr.readyState === 3 && xhr.status !== 200) {
+ return;
+ }
+ } catch (e) {
return;
}
if (xhr.readyState === 4 && xhr.status !== 200) {
if (p->fixed)
hit = !fixmatch(p->pattern, bol, p->ignore_case, &m);
- else
+ else {
+#ifdef REG_STARTEND
+ m.rm_so = 0;
+ m.rm_eo = *left_p;
+ hit = !regexec(&p->regexp, bol, 1, &m, REG_STARTEND);
+#else
hit = !regexec(&p->regexp, bol, 1, &m, 0);
+#endif
+ }
if (!hit || m.rm_so < 0 || m.rm_eo < 0)
continue;
if (earliest < 0 || m.rm_so < earliest)
:
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
sanitize_diff_raw='/^:/s/ '"$_x40"' '"$_x40"' \([A-Z]\)[0-9]* / X X \1# /'
compare_diff_raw () {
# When heuristics are improved, the score numbers would change.
. ./test-lib.sh
-q_to_nul () {
- perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
- tr Q '\015'
-}
-
-append_cr () {
- sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
- tr '\015' Q <"$1" | grep Q >/dev/null &&
- tr '\015' Q <"$1" | sed -ne 's/Q$//p'
+has_cr() {
+ tr '\015' Q <"$1" | grep Q >/dev/null
}
test_expect_success setup '
for f in one dir/two
do
- remove_cr "$f" >tmp && mv -f tmp $f &&
+ remove_cr <"$f" >tmp && mv -f tmp $f &&
git update-index -- $f || {
echo "Eh? $f"
false
for f in one dir/two
do
- if remove_cr "$f" >/dev/null
+ if has_cr "$f"
then
echo "Eh? $f"
false
git read-tree --reset -u HEAD &&
git apply patch.file &&
- test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+ test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
echo "Eh? apply without index"
false
}
git apply --index patch.file &&
test "$patched" = `git rev-parse :one` &&
- test "$patched" = "`remove_cr one | git hash-object --stdin`" || {
+ test "$patched" = "`remove_cr <one | git hash-object --stdin`" || {
echo "Eh? apply with --index"
false
}
git config core.autocrlf true &&
git read-tree --reset -u HEAD &&
- if remove_cr dir/two >/dev/null
+ if has_cr dir/two
then
echo "Huh?"
false
: happy
fi &&
- if remove_cr one >/dev/null
+ if has_cr one
then
: happy
else
false
fi &&
- if remove_cr three >/dev/null
+ if has_cr three
then
echo "Huh?"
false
echo "two crlf=input" >.gitattributes &&
git read-tree --reset -u HEAD &&
- if remove_cr dir/two >/dev/null
+ if has_cr dir/two
then
echo "Huh?"
false
echo "t* crlf" >.gitattributes &&
git read-tree --reset -u HEAD &&
- if remove_cr dir/two >/dev/null
+ if has_cr dir/two
then
: happy
else
false
fi &&
- if remove_cr three >/dev/null
+ if has_cr three
then
: happy
else
rm -rf tmp one dir .gitattributes patch.file three &&
git read-tree --reset -u HEAD &&
- if remove_cr one >/dev/null
+ if has_cr one
then
echo "Eh? one should not have CRLF"
false
else
: happy
fi &&
- remove_cr three >/dev/null || {
+ has_cr three || {
echo "Eh? three should still have CRLF"
false
}
git read-tree --reset HEAD &&
git checkout-index -f -q -u -a &&
- if remove_cr one >/dev/null
+ if has_cr one
then
echo "Eh? one should not have CRLF"
false
else
: happy
fi &&
- remove_cr three >/dev/null || {
+ has_cr three || {
echo "Eh? three should still have CRLF"
false
}
git checkout-index -u .gitattributes &&
git checkout-index -u one dir/two three &&
- if remove_cr one >/dev/null
+ if has_cr one
then
echo "Eh? one should not have CRLF"
false
else
: happy
fi &&
- remove_cr three >/dev/null || {
+ has_cr three || {
echo "Eh? three should still have CRLF"
false
}
git checkout-index -u one dir/two three &&
git checkout-index -u .gitattributes &&
- if remove_cr one >/dev/null
+ if has_cr one
then
echo "Eh? one should not have CRLF"
false
else
: happy
fi &&
- remove_cr three >/dev/null || {
+ has_cr three || {
echo "Eh? three should still have CRLF"
false
}
git checkout master~1 &&
git checkout master &&
- remove_cr .file2 >/dev/null
+ has_cr .file2
'
test_tick &&
git commit -m Initial &&
- sed -e "s/\$/\r/" "$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
+ append_cr <"$TEST_DIRECTORY"/t0022-crlf-rename.sh >elpmas &&
git add elpmas &&
rm -f sample &&
100644 X 0 Z/NN
EOF
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
-
check_result () {
git ls-files --stage | sed -e 's/ '"$_x40"' / X /' >current &&
test_cmp expected current
git read-tree -m "$1" "$2" && git ls-files --stage
}
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
compare_change () {
sed -n >current \
-e '/^--- /d; /^+++ /d; /^@@ /d;' \
'
. ./test-lib.sh
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
compare_change () {
sed >current \
-e '1{/^diff --git /d;}' \
--- /dev/null
+#!/bin/sh
+
+test_description='read-tree D/F conflict corner cases'
+
+. ./test-lib.sh
+
+maketree () {
+ (
+ rm -f .git/index .git/index.lock &&
+ git clean -d -f -f -q -x &&
+ name="$1" &&
+ shift &&
+ for it
+ do
+ path=$(expr "$it" : '\([^:]*\)') &&
+ mkdir -p $(dirname "$path") &&
+ echo "$it" >"$path" &&
+ git update-index --add "$path" || exit
+ done &&
+ git tag "$name" $(git write-tree)
+ )
+}
+
+settree () {
+ rm -f .git/index .git/index.lock &&
+ git clean -d -f -f -q -x &&
+ git read-tree "$1" &&
+ git checkout-index -f -q -u -a &&
+ git update-index --refresh
+}
+
+checkindex () {
+ git ls-files -s |
+ sed "s|^[0-7][0-7]* $_x40 \([0-3]\) |\1 |" >current &&
+ cat >expect &&
+ test_cmp expect current
+}
+
+test_expect_success setup '
+ maketree O-000 a/b-2/c/d a/b/c/d a/x &&
+ maketree A-000 a/b-2/c/d a/b/c/d a/x &&
+ maketree A-001 a/b-2/c/d a/b/c/d a/b/c/e a/x &&
+ maketree B-000 a/b-2/c/d a/b a/x &&
+
+ maketree O-010 t-0 t/1 t/2 t=3 &&
+ maketree A-010 t-0 t t=3 &&
+ maketree B-010 t/1: t=3: &&
+
+ maketree O-020 ds/dma/ioat.c ds/dma/ioat_dca.c &&
+ maketree A-020 ds/dma/ioat/Makefile ds/dma/ioat/registers.h &&
+ :
+'
+
+test_expect_success '3-way (1)' '
+ settree A-000 &&
+ git read-tree -m -u O-000 A-000 B-000 &&
+ checkindex <<-EOF
+ 3 a/b
+ 0 a/b-2/c/d
+ 1 a/b/c/d
+ 2 a/b/c/d
+ 0 a/x
+ EOF
+'
+
+test_expect_success '3-way (2)' '
+ settree A-001 &&
+ git read-tree -m -u O-000 A-001 B-000 &&
+ checkindex <<-EOF
+ 3 a/b
+ 0 a/b-2/c/d
+ 1 a/b/c/d
+ 2 a/b/c/d
+ 2 a/b/c/e
+ 0 a/x
+ EOF
+'
+
+test_expect_success '3-way (3)' '
+ settree A-010 &&
+ git read-tree -m -u O-010 A-010 B-010 &&
+ checkindex <<-EOF
+ 2 t
+ 1 t-0
+ 2 t-0
+ 1 t/1
+ 3 t/1
+ 1 t/2
+ 0 t=3
+ EOF
+'
+
+test_expect_success '2-way (1)' '
+ settree O-020 &&
+ git read-tree -m -u O-020 A-020 &&
+ checkindex <<-EOF
+ 0 ds/dma/ioat/Makefile
+ 0 ds/dma/ioat/registers.h
+ EOF
+'
+
+test_done
+++ /dev/null
-#!/bin/sh
-
-test_description='test <branch>@{upstream} syntax'
-
-. ./test-lib.sh
-
-
-test_expect_success 'setup' '
-
- test_commit 1 &&
- git checkout -b side &&
- test_commit 2 &&
- git checkout master &&
- git clone . clone &&
- test_commit 3 &&
- (cd clone &&
- test_commit 4 &&
- git branch --track my-side origin/side)
-
-'
-
-full_name () {
- (cd clone &&
- git rev-parse --symbolic-full-name "$@")
-}
-
-commit_subject () {
- (cd clone &&
- git show -s --pretty=format:%s "$@")
-}
-
-test_expect_success '@{upstream} resolves to correct full name' '
- test refs/remotes/origin/master = "$(full_name @{upstream})"
-'
-
-test_expect_success '@{u} resolves to correct full name' '
- test refs/remotes/origin/master = "$(full_name @{u})"
-'
-
-test_expect_success 'my-side@{upstream} resolves to correct full name' '
- test refs/remotes/origin/side = "$(full_name my-side@{u})"
-'
-
-test_expect_success 'my-side@{u} resolves to correct commit' '
- git checkout side &&
- test_commit 5 &&
- (cd clone && git fetch) &&
- test 2 = "$(commit_subject my-side)" &&
- test 5 = "$(commit_subject my-side@{u})"
-'
-
-test_expect_success 'not-tracking@{u} fails' '
- test_must_fail full_name non-tracking@{u} &&
- (cd clone && git checkout --no-track -b non-tracking) &&
- test_must_fail full_name non-tracking@{u}
-'
-
-test_expect_success '<branch>@{u}@{1} resolves correctly' '
- test_commit 6 &&
- (cd clone && git fetch) &&
- test 5 = $(commit_subject my-side@{u}@{1})
-'
-
-test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
- git checkout HEAD^0 &&
- test_must_fail git rev-parse @{u}
-'
-
-test_expect_success 'checkout -b new my-side@{u} forks from the same' '
-(
- cd clone &&
- git checkout -b new my-side@{u} &&
- git rev-parse --symbolic-full-name my-side@{u} >expect &&
- git rev-parse --symbolic-full-name new@{u} >actual &&
- test_cmp expect actual
-)
-'
-
-test_expect_success 'merge my-side@{u} records the correct name' '
-(
- sq="'\''" &&
- cd clone || exit
- git checkout master || exit
- git branch -D new ;# can fail but is ok
- git branch -t new my-side@{u} &&
- git merge -s ours new@{u} &&
- git show -s --pretty=format:%s >actual &&
- echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
- test_cmp expect actual
-)
-'
-
-test_expect_success 'branch -d other@{u}' '
- git checkout -t -b other master &&
- git branch -d @{u} &&
- git for-each-ref refs/heads/master >actual &&
- >expect &&
- test_cmp expect actual
-'
-
-test_expect_success 'checkout other@{u}' '
- git branch -f master HEAD &&
- git checkout -t -b another master &&
- git checkout @{u} &&
- git symbolic-ref HEAD >actual &&
- echo refs/heads/master >expect &&
- test_cmp expect actual
-'
-
-test_done
--- /dev/null
+#!/bin/sh
+
+test_description='test <branch>@{upstream} syntax'
+
+. ./test-lib.sh
+
+
+test_expect_success 'setup' '
+
+ test_commit 1 &&
+ git checkout -b side &&
+ test_commit 2 &&
+ git checkout master &&
+ git clone . clone &&
+ test_commit 3 &&
+ (cd clone &&
+ test_commit 4 &&
+ git branch --track my-side origin/side)
+
+'
+
+full_name () {
+ (cd clone &&
+ git rev-parse --symbolic-full-name "$@")
+}
+
+commit_subject () {
+ (cd clone &&
+ git show -s --pretty=format:%s "$@")
+}
+
+test_expect_success '@{upstream} resolves to correct full name' '
+ test refs/remotes/origin/master = "$(full_name @{upstream})"
+'
+
+test_expect_success '@{u} resolves to correct full name' '
+ test refs/remotes/origin/master = "$(full_name @{u})"
+'
+
+test_expect_success 'my-side@{upstream} resolves to correct full name' '
+ test refs/remotes/origin/side = "$(full_name my-side@{u})"
+'
+
+test_expect_success 'my-side@{u} resolves to correct commit' '
+ git checkout side &&
+ test_commit 5 &&
+ (cd clone && git fetch) &&
+ test 2 = "$(commit_subject my-side)" &&
+ test 5 = "$(commit_subject my-side@{u})"
+'
+
+test_expect_success 'not-tracking@{u} fails' '
+ test_must_fail full_name non-tracking@{u} &&
+ (cd clone && git checkout --no-track -b non-tracking) &&
+ test_must_fail full_name non-tracking@{u}
+'
+
+test_expect_success '<branch>@{u}@{1} resolves correctly' '
+ test_commit 6 &&
+ (cd clone && git fetch) &&
+ test 5 = $(commit_subject my-side@{u}@{1})
+'
+
+test_expect_success '@{u} without specifying branch fails on a detached HEAD' '
+ git checkout HEAD^0 &&
+ test_must_fail git rev-parse @{u}
+'
+
+test_expect_success 'checkout -b new my-side@{u} forks from the same' '
+(
+ cd clone &&
+ git checkout -b new my-side@{u} &&
+ git rev-parse --symbolic-full-name my-side@{u} >expect &&
+ git rev-parse --symbolic-full-name new@{u} >actual &&
+ test_cmp expect actual
+)
+'
+
+test_expect_success 'merge my-side@{u} records the correct name' '
+(
+ sq="'\''" &&
+ cd clone || exit
+ git checkout master || exit
+ git branch -D new ;# can fail but is ok
+ git branch -t new my-side@{u} &&
+ git merge -s ours new@{u} &&
+ git show -s --pretty=format:%s >actual &&
+ echo "Merge remote branch ${sq}origin/side${sq}" >expect &&
+ test_cmp expect actual
+)
+'
+
+test_expect_success 'branch -d other@{u}' '
+ git checkout -t -b other master &&
+ git branch -d @{u} &&
+ git for-each-ref refs/heads/master >actual &&
+ >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'checkout other@{u}' '
+ git branch -f master HEAD &&
+ git checkout -t -b another master &&
+ git checkout @{u} &&
+ git symbolic-ref HEAD >actual &&
+ echo refs/heads/master >expect &&
+ test_cmp expect actual
+'
+
+test_done
tree=`git write-tree` &&
echo $tree'
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
test_output () {
sed -e "s/ $_x40 / X /" <current >check
test_cmp expected check
tree=`git write-tree` &&
echo $tree'
-_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
test_output () {
sed -e "s/ $_x40 / X /" <current >check
test_cmp expected check
test ! -s output.out
'
-q_to_cr () {
- tr Q '\015'
-}
-
test_expect_success 'Rebase a commit that sprinkles CRs in' '
(
echo "One"
git checkout side &&
git rebase master &&
- git cat-file commit HEAD | sed -e "1,/^$/d" >actual &&
- git cat-file commit side@{1} | sed -e "1,/^$/d" >expect &&
+ git cat-file commit HEAD | sed -e "1,/^\$/d" >actual &&
+ git cat-file commit side@{1} | sed -e "1,/^\$/d" >expect &&
test_cmp expect actual
'
'test_chmod +x rezrov &&
git diff-index $tree >current'
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
sed -e 's/\(:100644 100755\) \('"$_x40"'\) \2 /\1 X X /' <current >check
echo ":100644 100755 X X M rezrov" >expected
tree1=`git write-tree` &&
test "$tree1" = "$tree0"'
-q_to_nul() {
- perl -pe 'y/Q/\000/'
-}
-
nul_to_q() {
perl -pe 'y/\000/Q/'
}
git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>
" &&
git format-patch --stdout master..side > patch2 &&
- sed -e "/^$/q" patch2 > hdrs2 &&
- grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
- grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
+ sed -e "/^\$/q" patch2 > hdrs2 &&
+ grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs2 &&
+ grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs2
'
git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
git config --add format.headers "Cc: S. E. Cipient <scipient@example.com>" &&
git format-patch --stdout master..side >patch3 &&
- sed -e "/^$/q" patch3 > hdrs3 &&
- grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
- grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
+ sed -e "/^\$/q" patch3 > hdrs3 &&
+ grep "^To: R. E. Cipient <rcipient@example.com>\$" hdrs3 &&
+ grep "^Cc: S. E. Cipient <scipient@example.com>\$" hdrs3
'
git config --replace-all format.headers "To: R. E. Cipient <rcipient@example.com>" &&
git config --add format.headers "To: S. E. Cipient <scipient@example.com>" &&
git format-patch --stdout master..side > patch4 &&
- sed -e "/^$/q" patch4 > hdrs4 &&
- grep "^To: R. E. Cipient <rcipient@example.com>,$" hdrs4 &&
- grep "^ *S. E. Cipient <scipient@example.com>$" hdrs4
+ sed -e "/^\$/q" patch4 > hdrs4 &&
+ grep "^To: R. E. Cipient <rcipient@example.com>,\$" hdrs4 &&
+ grep "^ *S. E. Cipient <scipient@example.com>\$" hdrs4
'
test_expect_success 'additional command line cc' '
git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
- git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch5 &&
- grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch5 &&
- grep "^ *S. E. Cipient <scipient@example.com>$" patch5
+ git format-patch --cc="S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch5 &&
+ grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch5 &&
+ grep "^ *S. E. Cipient <scipient@example.com>\$" patch5
'
test_expect_success 'command line headers' '
git config --unset-all format.headers &&
- git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch6 &&
- grep "^Cc: R. E. Cipient <rcipient@example.com>$" patch6
+ git format-patch --add-header="Cc: R. E. Cipient <rcipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch6 &&
+ grep "^Cc: R. E. Cipient <rcipient@example.com>\$" patch6
'
test_expect_success 'configuration headers and command line headers' '
git config --replace-all format.headers "Cc: R. E. Cipient <rcipient@example.com>" &&
- git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^$/q" >patch7 &&
- grep "^Cc: R. E. Cipient <rcipient@example.com>,$" patch7 &&
- grep "^ *S. E. Cipient <scipient@example.com>$" patch7
+ git format-patch --add-header="Cc: S. E. Cipient <scipient@example.com>" --stdout master..side | sed -e "/^\$/q" >patch7 &&
+ grep "^Cc: R. E. Cipient <rcipient@example.com>,\$" patch7 &&
+ grep "^ *S. E. Cipient <scipient@example.com>\$" patch7
'
test_expect_success 'multiple files' '
git mv file foo &&
git commit -m foo &&
git format-patch --cover-letter -1 &&
- ! grep "file => foo .* 0 *$" 0000-cover-letter.patch &&
+ ! grep "file => foo .* 0 *\$" 0000-cover-letter.patch &&
git format-patch --cover-letter -1 -M &&
- grep "file => foo .* 0 *$" 0000-cover-letter.patch
+ grep "file => foo .* 0 *\$" 0000-cover-letter.patch
'
test_expect_success 'shortlog of cover-letter wraps overly-long onelines' '
git format-patch --cover-letter -2 &&
- sed -e "1,/A U Thor/d" -e "/^$/q" < 0000-cover-letter.patch > output &&
+ sed -e "1,/A U Thor/d" -e "/^\$/q" < 0000-cover-letter.patch > output &&
test_cmp expect output
'
test_expect_success 'format-patch respects -U' '
git format-patch -U4 -2 &&
- sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+ sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
test_cmp expect output
'
test_expect_success 'format-patch -p suppresses stat' '
git format-patch -p -2 &&
- sed -e "1,/^$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
+ sed -e "1,/^\$/d" -e "/^+5/q" < 0001-This-is-an-excessively-long-subject-line-for-a-messa.patch > output &&
test_cmp expect output
'
# patch-2 is the same as patch-1 but is based
# on a version that already has whitespace fixed,
# and does not introduce whitespace breakages.
- sed -e "s/ $//" patch-1 >patch-2 &&
+ sed -e "s/ \$//" patch-1 >patch-2 &&
# If all whitespace breakages are fixed the contents
# should look like file-fixed
- sed -e "s/ $//" file-1 >file-fixed
+ sed -e "s/ \$//" file-1 >file-fixed
'
echo "X-Fake-Field: Line Three" &&
git format-patch --stdout first | sed -e "1d"
} > patch1.eml &&
+ {
+ echo "X-Fake-Field: Line One" &&
+ echo "X-Fake-Field: Line Two" &&
+ echo "X-Fake-Field: Line Three" &&
+ git format-patch --stdout first | sed -e "1d"
+ } | append_cr >patch1-crlf.eml &&
sed -n -e "3,\$p" msg >file &&
git add file &&
test_tick &&
test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
'
+test_expect_success 'am applies patch e-mail not in a mbox with CRLF' '
+ git checkout first &&
+ git am patch1-crlf.eml &&
+ ! test -d .git/rebase-apply &&
+ test -z "$(git diff second)" &&
+ test "$(git rev-parse second)" = "$(git rev-parse HEAD)" &&
+ test "$(git rev-parse second^)" = "$(git rev-parse HEAD^)"
+'
+
GIT_AUTHOR_NAME="Another Thor"
GIT_AUTHOR_EMAIL="a.thor@example.com"
GIT_COMMITTER_NAME="Co M Miter"
git checkout first &&
test_tick &&
git am --committer-date-is-author-date patch1 &&
- git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+ git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
at=$(sed -ne "/^author /s/.*> //p" head1) &&
ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
test "$at" = "$ct"
git checkout first &&
test_tick &&
git am patch1 &&
- git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+ git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
at=$(sed -ne "/^author /s/.*> //p" head1) &&
ct=$(sed -ne "/^committer /s/.*> //p" head1) &&
test "$at" != "$ct"
git checkout first &&
test_tick &&
git am --ignore-date patch1 &&
- git cat-file commit HEAD | sed -e "/^$/q" >head1 &&
+ git cat-file commit HEAD | sed -e "/^\$/q" >head1 &&
at=$(sed -ne "/^author /s/.*> //p" head1) &&
echo "$at" | grep "+0000"
'
test_expect_success 'log --graph with merge' '
git log --graph --date-order --pretty=tformat:%s |
- sed "s/ *$//" >actual &&
+ sed "s/ *\$//" >actual &&
test_cmp expect actual
'
test_expect_success 'log --graph with full output' '
git log --graph --date-order --pretty=short |
git name-rev --name-only --stdin |
- sed "s/Merge:.*/Merge: A B/;s/ *$//" >actual &&
+ sed "s/Merge:.*/Merge: A B/;s/ *\$//" >actual &&
test_cmp expect actual
'
test_expect_success 'log --graph with merge' '
git log --graph --date-order --pretty=tformat:%s |
- sed "s/ *$//" >actual &&
+ sed "s/ *\$//" >actual &&
test_cmp expect actual
'
--- /dev/null
+#!/bin/sh
+
+test_description='git pull message generation'
+
+. ./test-lib.sh
+
+dollar='$Dollar'
+
+test_expect_success setup '
+ test_commit initial afile original &&
+ git clone . cloned &&
+ (
+ cd cloned &&
+ echo added >bfile &&
+ git add bfile &&
+ test_tick &&
+ git commit -m "add bfile"
+ ) &&
+ test_tick && test_tick &&
+ echo "original $dollar" >afile &&
+ git add afile &&
+ git commit -m "do not clobber $dollar signs"
+'
+
+test_expect_success pull '
+(
+ cd cloned &&
+ git pull --log &&
+ git log -2 &&
+ git cat-file commit HEAD >result &&
+ grep Dollar result
+)
+'
+
+test_done
'
test_expect_success 'non-fast-forward push shows help message' '
- grep \
-"To prevent you from losing history, non-fast-forward updates were rejected
-Merge the remote changes before pushing again. See the '"'non-fast-forward'"'
-section of '"'git push --help'"' for details." output
+ grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+ output
'
test_expect_success 'push fails for non-fast-forward refs unmatched by remote helper' '
grep "^ + [a-f0-9]*\.\.\.[a-f0-9]* *master -> master (forced update)$" output &&
grep "^ ! \[rejected\] *master -> retsam (non-fast-forward)$" output &&
- grep \
-"To prevent you from losing history, non-fast-forward updates were rejected
-Merge the remote changes before pushing again. See the '"'non-fast-forward'"'
-section of '"'git push --help'"' for details." output
+ grep "To prevent you from losing history, non-fast-forward updates were rejected" \
+ output
'
stop_httpd
git tag "$1"
}
-_x40='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
-_x40="$_x40$_x40$_x40$_x40$_x40$_x40$_x40$_x40"
-
unnote () {
git name-rev --tags --stdin | sed -e "s|$_x40 (tags/\([^)]*\)) |\1 |g"
}
grep "Cannot merge binary files" merge.err
'
-sed -e "s/deerit.$/deerit;/" -e "s/me;$/me./" < new5.txt > new6.txt
-sed -e "s/deerit.$/deerit,/" -e "s/me;$/me,/" < new5.txt > new7.txt
+sed -e "s/deerit.\$/deerit;/" -e "s/me;\$/me./" < new5.txt > new6.txt
+sed -e "s/deerit.\$/deerit,/" -e "s/me;\$/me,/" < new5.txt > new7.txt
test_expect_success 'MERGE_ZEALOUS simplifies non-conflicts' '
#!/bin/sh
-append_cr () {
- sed -e 's/$/Q/' | tr Q '\015'
-}
-
-remove_cr () {
- tr '\015' Q | sed -e 's/Q$//'
-}
-
test_description='merge conflict in crlf repo
b---M
git tag baseline
'
-test_expect_success 'do not lose a/b-2/c/d in merge (resolve)' '
+test_expect_failure 'do not lose a/b-2/c/d in merge (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s resolve master &&
git tag test2
'
-test_expect_failure 'merge should not have conflicts (resolve)' '
+test_expect_success 'merge should not have conflicts (resolve)' '
git reset --hard &&
git checkout baseline^0 &&
git merge -s resolve test2 &&
test_expect_success '--signoff' '
echo "yet another content *narf*" >> foo &&
echo "zort" | git commit -s -F - foo &&
- git cat-file commit HEAD | sed "1,/^$/d" > output &&
+ git cat-file commit HEAD | sed "1,/^\$/d" > output &&
test_cmp expect output
'
--smtp-server="$(pwd)/fake.sendmail" \
$patches \
2>errors &&
- grep "^From: Example <from@example.com>$" msgtxt1 &&
- grep "^To: to@example.com$" msgtxt1
+ grep "^From: Example <from@example.com>\$" msgtxt1 &&
+ grep "^To: to@example.com\$" msgtxt1
'
test_expect_success 'cccmd works' '
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
- sed "1,/^$/d" < msgtxt1 > msgbody1
+ sed "1,/^\$/d" < msgtxt1 > msgbody1
grep "From: A <author@example.com>" msgbody1
'
--to=nobody@example.com \
--smtp-server="$(pwd)/fake.sendmail" \
$patches &&
- sed "1,/^$/d" < msgtxt1 > msgbody1
+ sed "1,/^\$/d" < msgtxt1 > msgbody1
! grep "From: A <author@example.com>" msgbody1
'
test_expect_success 'pserver authentication' \
'cat request-anonymous | git-cvsserver pserver >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
test_expect_success 'pserver authentication failure (non-anonymous user)' \
'if cat request-git | git-cvsserver pserver >log 2>&1
else
true
fi &&
- sed -ne \$p log | grep "^I HATE YOU$"'
+ sed -ne \$p log | grep "^I HATE YOU\$"'
test_expect_success 'pserver authentication (login)' \
'cat login-anonymous | git-cvsserver pserver >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
test_expect_success 'pserver authentication failure (login/non-anonymous user)' \
'if cat login-git | git-cvsserver pserver >log 2>&1
else
true
fi &&
- sed -ne \$p log | grep "^I HATE YOU$"'
+ sed -ne \$p log | grep "^I HATE YOU\$"'
# misuse pserver authentication for testing of req_Root
test_expect_success 'req_Root (strict paths)' \
'cat request-anonymous | git-cvsserver --strict-paths pserver "$SERVERDIR" >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
test_expect_success 'req_Root failure (strict-paths)' '
! cat request-anonymous |
test_expect_success 'req_Root (w/o strict-paths)' \
'cat request-anonymous | git-cvsserver pserver "$WORKDIR/" >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
test_expect_success 'req_Root failure (w/o strict-paths)' '
! cat request-anonymous |
test_expect_success 'req_Root (base-path)' \
'cat request-base | git-cvsserver --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
test_expect_success 'req_Root failure (base-path)' '
! cat request-anonymous |
test_expect_success 'req_Root (export-all)' \
'cat request-anonymous | git-cvsserver --export-all pserver "$WORKDIR" >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
test_expect_success 'req_Root failure (export-all w/o whitelist)' \
'! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)'
test_expect_success 'req_Root (everything together)' \
'cat request-base | git-cvsserver --export-all --strict-paths --base-path "$WORKDIR/" pserver "$SERVERDIR" >log 2>&1 &&
- sed -ne \$p log | grep "^I LOVE YOU$"'
+ sed -ne \$p log | grep "^I LOVE YOU\$"'
GIT_DIR="$SERVERDIR" git config --bool gitcvs.enabled true || exit 1
. ./test-lib.sh
-q_to_nul () {
- perl -pe 'y/Q/\000/'
-}
-
-q_to_cr () {
- tr Q '\015'
-}
-
marked_as () {
foundEntry="$(grep "^/$2/" "$1/CVS/Entries")"
if [ x"$foundEntry" = x"" ] ; then
;;
esac
+# Convenience
+#
+# A regexp to match 5 and 40 hexdigits
+_x05='[0-9a-f][0-9a-f][0-9a-f][0-9a-f][0-9a-f]'
+_x40="$_x05$_x05$_x05$_x05$_x05$_x05$_x05$_x05"
+
# Each test should start with something like this, after copyright notices:
#
# test_description='Description of this test...
-e 's/.\[m/<RESET>/g'
}
+q_to_nul () {
+ perl -pe 'y/Q/\000/'
+}
+
+q_to_cr () {
+ tr Q '\015'
+}
+
+append_cr () {
+ sed -e 's/$/Q/' | tr Q '\015'
+}
+
+remove_cr () {
+ tr '\015' Q | sed -e 's/Q$//'
+}
+
test_tick () {
if test -z "${test_tick+set}"
then
return buf;
}
-static int entry_compare(struct name_entry *a, struct name_entry *b)
-{
- return df_name_compare(
- a->path, tree_entry_len(a->path, a->sha1), a->mode,
- b->path, tree_entry_len(b->path, b->sha1), b->mode);
-}
-
static void entry_clear(struct name_entry *a)
{
memset(a, 0, sizeof(*a));
return path;
}
+struct tree_desc_skip {
+ struct tree_desc_skip *prev;
+ const void *ptr;
+};
+
+struct tree_desc_x {
+ struct tree_desc d;
+ struct tree_desc_skip *skip;
+};
+
+static int name_compare(const char *a, int a_len,
+ const char *b, int b_len)
+{
+ int len = (a_len < b_len) ? a_len : b_len;
+ int cmp = memcmp(a, b, len);
+ if (cmp)
+ return cmp;
+ return (a_len - b_len);
+}
+
+static int check_entry_match(const char *a, int a_len, const char *b, int b_len)
+{
+ /*
+ * The caller wants to pick *a* from a tree or nothing.
+ * We are looking at *b* in a tree.
+ *
+ * (0) If a and b are the same name, we are trivially happy.
+ *
+ * There are three possibilities where *a* could be hiding
+ * behind *b*.
+ *
+ * (1) *a* == "t", *b* == "ab" i.e. *b* sorts earlier than *a* no
+ * matter what.
+ * (2) *a* == "t", *b* == "t-2" and "t" is a subtree in the tree;
+ * (3) *a* == "t-2", *b* == "t" and "t-2" is a blob in the tree.
+ *
+ * Otherwise we know *a* won't appear in the tree without
+ * scanning further.
+ */
+
+ int cmp = name_compare(a, a_len, b, b_len);
+
+ /* Most common case first -- reading sync'd trees */
+ if (!cmp)
+ return cmp;
+
+ if (0 < cmp) {
+ /* a comes after b; it does not matter if it is case (3)
+ if (b_len < a_len && !memcmp(a, b, b_len) && a[b_len] < '/')
+ return 1;
+ */
+ return 1; /* keep looking */
+ }
+
+ /* b comes after a; are we looking at case (2)? */
+ if (a_len < b_len && !memcmp(a, b, a_len) && b[a_len] < '/')
+ return 1; /* keep looking */
+
+ return -1; /* a cannot appear in the tree */
+}
+
+/*
+ * From the extended tree_desc, extract the first name entry, while
+ * paying attention to the candidate "first" name. Most importantly,
+ * when looking for an entry, if there are entries that sorts earlier
+ * in the tree object representation than that name, skip them and
+ * process the named entry first. We will remember that we haven't
+ * processed the first entry yet, and in the later call skip the
+ * entry we processed early when update_extended_entry() is called.
+ *
+ * E.g. if the underlying tree object has these entries:
+ *
+ * blob "t-1"
+ * blob "t-2"
+ * tree "t"
+ * blob "t=1"
+ *
+ * and the "first" asks for "t", remember that we still need to
+ * process "t-1" and "t-2" but extract "t". After processing the
+ * entry "t" from this call, the caller will let us know by calling
+ * update_extended_entry() that we can remember "t" has been processed
+ * already.
+ */
+
+static void extended_entry_extract(struct tree_desc_x *t,
+ struct name_entry *a,
+ const char *first,
+ int first_len)
+{
+ const char *path;
+ int len;
+ struct tree_desc probe;
+ struct tree_desc_skip *skip;
+
+ /*
+ * Extract the first entry from the tree_desc, but skip the
+ * ones that we already returned in earlier rounds.
+ */
+ while (1) {
+ if (!t->d.size) {
+ entry_clear(a);
+ break; /* not found */
+ }
+ entry_extract(&t->d, a);
+ for (skip = t->skip; skip; skip = skip->prev)
+ if (a->path == skip->ptr)
+ break; /* found */
+ if (!skip)
+ break;
+ /* We have processed this entry already. */
+ update_tree_entry(&t->d);
+ }
+
+ if (!first || !a->path)
+ return;
+
+ /*
+ * The caller wants "first" from this tree, or nothing.
+ */
+ path = a->path;
+ len = tree_entry_len(a->path, a->sha1);
+ switch (check_entry_match(first, first_len, path, len)) {
+ case -1:
+ entry_clear(a);
+ case 0:
+ return;
+ default:
+ break;
+ }
+
+ /*
+ * We need to look-ahead -- we suspect that a subtree whose
+ * name is "first" may be hiding behind the current entry "path".
+ */
+ probe = t->d;
+ while (probe.size) {
+ entry_extract(&probe, a);
+ path = a->path;
+ len = tree_entry_len(a->path, a->sha1);
+ switch (check_entry_match(first, first_len, path, len)) {
+ case -1:
+ entry_clear(a);
+ case 0:
+ return;
+ default:
+ update_tree_entry(&probe);
+ break;
+ }
+ /* keep looking */
+ }
+ entry_clear(a);
+}
+
+static void update_extended_entry(struct tree_desc_x *t, struct name_entry *a)
+{
+ if (t->d.entry.path == a->path) {
+ update_tree_entry(&t->d);
+ } else {
+ /* we have returned this entry early */
+ struct tree_desc_skip *skip = xmalloc(sizeof(*skip));
+ skip->ptr = a->path;
+ skip->prev = t->skip;
+ t->skip = skip;
+ }
+}
+
+static void free_extended_entry(struct tree_desc_x *t)
+{
+ struct tree_desc_skip *p, *s;
+
+ for (s = t->skip; s; s = p) {
+ p = s->prev;
+ free(s);
+ }
+}
+
int traverse_trees(int n, struct tree_desc *t, struct traverse_info *info)
{
int ret = 0;
struct name_entry *entry = xmalloc(n*sizeof(*entry));
+ int i;
+ struct tree_desc_x *tx = xcalloc(n, sizeof(*tx));
+
+ for (i = 0; i < n; i++)
+ tx[i].d = t[i];
for (;;) {
- unsigned long mask = 0;
- unsigned long dirmask = 0;
- int i, last;
+ unsigned long mask, dirmask;
+ const char *first = NULL;
+ int first_len = 0;
+ struct name_entry *e;
+ int len;
- last = -1;
for (i = 0; i < n; i++) {
- if (!t[i].size)
+ e = entry + i;
+ extended_entry_extract(tx + i, e, NULL, 0);
+ }
+
+ /*
+ * A tree may have "t-2" at the current location even
+ * though it may have "t" that is a subtree behind it,
+ * and another tree may return "t". We want to grab
+ * all "t" from all trees to match in such a case.
+ */
+ for (i = 0; i < n; i++) {
+ e = entry + i;
+ if (!e->path)
continue;
- entry_extract(t+i, entry+i);
- if (last >= 0) {
- int cmp = entry_compare(entry+i, entry+last);
-
- /*
- * Is the new name bigger than the old one?
- * Ignore it
- */
- if (cmp > 0)
+ len = tree_entry_len(e->path, e->sha1);
+ if (!first) {
+ first = e->path;
+ first_len = len;
+ continue;
+ }
+ if (name_compare(e->path, len, first, first_len) < 0) {
+ first = e->path;
+ first_len = len;
+ }
+ }
+
+ if (first) {
+ for (i = 0; i < n; i++) {
+ e = entry + i;
+ extended_entry_extract(tx + i, e, first, first_len);
+ /* Cull the ones that are not the earliest */
+ if (!e->path)
continue;
- /*
- * Is the new name smaller than the old one?
- * Ignore all old ones
- */
- if (cmp < 0)
- mask = 0;
+ len = tree_entry_len(e->path, e->sha1);
+ if (name_compare(e->path, len, first, first_len))
+ entry_clear(e);
}
+ }
+
+ /* Now we have in entry[i] the earliest name from the trees */
+ mask = 0;
+ dirmask = 0;
+ for (i = 0; i < n; i++) {
+ if (!entry[i].path)
+ continue;
mask |= 1ul << i;
if (S_ISDIR(entry[i].mode))
dirmask |= 1ul << i;
- last = i;
}
if (!mask)
break;
- dirmask &= mask;
-
- /*
- * Clear all the unused name-entries.
- */
- for (i = 0; i < n; i++) {
- if (mask & (1ul << i))
- continue;
- entry_clear(entry + i);
- }
ret = info->fn(n, mask, dirmask, entry, info);
if (ret < 0)
break;
- if (ret)
- mask &= ret;
+ mask &= ret;
ret = 0;
- for (i = 0; i < n; i++) {
+ for (i = 0; i < n; i++)
if (mask & (1ul << i))
- update_tree_entry(t + i);
- }
+ update_extended_entry(tx + i, entry + i);
}
free(entry);
+ for (i = 0; i < n; i++)
+ free_extended_entry(tx + i);
+ free(tx);
return ret;
}
return ret;
}
-static int unpack_index_entry(struct cache_entry *ce, struct unpack_trees_options *o)
+static void mark_ce_used(struct cache_entry *ce, struct unpack_trees_options *o)
+{
+ ce->ce_flags |= CE_UNPACKED;
+
+ if (o->cache_bottom < o->src_index->cache_nr &&
+ o->src_index->cache[o->cache_bottom] == ce) {
+ int bottom = o->cache_bottom;
+ while (bottom < o->src_index->cache_nr &&
+ o->src_index->cache[bottom]->ce_flags & CE_UNPACKED)
+ bottom++;
+ o->cache_bottom = bottom;
+ }
+}
+
+static void mark_all_ce_unused(struct index_state *index)
+{
+ int i;
+ for (i = 0; i < index->cache_nr; i++)
+ index->cache[i]->ce_flags &= ~CE_UNPACKED;
+}
+
+static int locate_in_src_index(struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ struct index_state *index = o->src_index;
+ int len = ce_namelen(ce);
+ int pos = index_name_pos(index, ce->name, len);
+ if (pos < 0)
+ pos = -1 - pos;
+ return pos;
+}
+
+/*
+ * We call unpack_index_entry() with an unmerged cache entry
+ * only in diff-index, and it wants a single callback. Skip
+ * the other unmerged entry with the same name.
+ */
+static void mark_ce_used_same_name(struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ struct index_state *index = o->src_index;
+ int len = ce_namelen(ce);
+ int pos;
+
+ for (pos = locate_in_src_index(ce, o); pos < index->cache_nr; pos++) {
+ struct cache_entry *next = index->cache[pos];
+ if (len != ce_namelen(next) ||
+ memcmp(ce->name, next->name, len))
+ break;
+ mark_ce_used(next, o);
+ }
+}
+
+static struct cache_entry *next_cache_entry(struct unpack_trees_options *o)
+{
+ const struct index_state *index = o->src_index;
+ int pos = o->cache_bottom;
+
+ while (pos < index->cache_nr) {
+ struct cache_entry *ce = index->cache[pos];
+ if (!(ce->ce_flags & CE_UNPACKED))
+ return ce;
+ pos++;
+ }
+ return NULL;
+}
+
+static void add_same_unmerged(struct cache_entry *ce,
+ struct unpack_trees_options *o)
+{
+ struct index_state *index = o->src_index;
+ int len = ce_namelen(ce);
+ int pos = index_name_pos(index, ce->name, len);
+
+ if (0 <= pos)
+ die("programming error in a caller of mark_ce_used_same_name");
+ for (pos = -pos - 1; pos < index->cache_nr; pos++) {
+ struct cache_entry *next = index->cache[pos];
+ if (len != ce_namelen(next) ||
+ memcmp(ce->name, next->name, len))
+ break;
+ add_entry(o, next, 0, 0);
+ mark_ce_used(next, o);
+ }
+}
+
+static int unpack_index_entry(struct cache_entry *ce,
+ struct unpack_trees_options *o)
{
struct cache_entry *src[5] = { ce, NULL, };
+ int ret;
- o->pos++;
+ mark_ce_used(ce, o);
if (ce_stage(ce)) {
if (o->skip_unmerged) {
add_entry(o, ce, 0, 0);
return 0;
}
}
- return call_unpack_fn(src, o);
+ ret = call_unpack_fn(src, o);
+ if (ce_stage(ce))
+ mark_ce_used_same_name(ce, o);
+ return ret;
+}
+
+static int find_cache_pos(struct traverse_info *, const struct name_entry *);
+
+static void restore_cache_bottom(struct traverse_info *info, int bottom)
+{
+ struct unpack_trees_options *o = info->data;
+
+ if (o->diff_index_cached)
+ return;
+ o->cache_bottom = bottom;
+}
+
+static int switch_cache_bottom(struct traverse_info *info)
+{
+ struct unpack_trees_options *o = info->data;
+ int ret, pos;
+
+ if (o->diff_index_cached)
+ return 0;
+ ret = o->cache_bottom;
+ pos = find_cache_pos(info->prev, &info->name);
+
+ if (pos < -1)
+ o->cache_bottom = -2 - pos;
+ else if (pos < 0)
+ o->cache_bottom = o->src_index->cache_nr;
+ return ret;
}
static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
{
- int i;
+ int i, ret, bottom;
struct tree_desc t[MAX_UNPACK_TREES];
struct traverse_info newinfo;
struct name_entry *p;
sha1 = names[i].sha1;
fill_tree_descriptor(t+i, sha1);
}
- return traverse_trees(n, t, &newinfo);
+
+ bottom = switch_cache_bottom(&newinfo);
+ ret = traverse_trees(n, t, &newinfo);
+ restore_cache_bottom(&newinfo, bottom);
+ return ret;
}
/*
return ce_namelen(ce) > traverse_path_len(info, n);
}
+static int ce_in_traverse_path(const struct cache_entry *ce,
+ const struct traverse_info *info)
+{
+ if (!info->prev)
+ return 1;
+ if (do_compare_entry(ce, info->prev, &info->name))
+ return 0;
+ /*
+ * If ce (blob) is the same name as the path (which is a tree
+ * we will be descending into), it won't be inside it.
+ */
+ return (info->pathlen < ce_namelen(ce));
+}
+
static struct cache_entry *create_ce_entry(const struct traverse_info *info, const struct name_entry *n, int stage)
{
int len = traverse_path_len(info, n);
return -1;
}
+/* NEEDSWORK: give this a better name and share with tree-walk.c */
+static int name_compare(const char *a, int a_len,
+ const char *b, int b_len)
+{
+ int len = (a_len < b_len) ? a_len : b_len;
+ int cmp = memcmp(a, b, len);
+ if (cmp)
+ return cmp;
+ return (a_len - b_len);
+}
+
+/*
+ * The tree traversal is looking at name p. If we have a matching entry,
+ * return it. If name p is a directory in the index, do not return
+ * anything, as we will want to match it when the traversal descends into
+ * the directory.
+ */
+static int find_cache_pos(struct traverse_info *info,
+ const struct name_entry *p)
+{
+ int pos;
+ struct unpack_trees_options *o = info->data;
+ struct index_state *index = o->src_index;
+ int pfxlen = info->pathlen;
+ int p_len = tree_entry_len(p->path, p->sha1);
+
+ for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
+ struct cache_entry *ce = index->cache[pos];
+ const char *ce_name, *ce_slash;
+ int cmp, ce_len;
+
+ if (!ce_in_traverse_path(ce, info))
+ continue;
+ if (ce->ce_flags & CE_UNPACKED)
+ continue;
+ ce_name = ce->name + pfxlen;
+ ce_slash = strchr(ce_name, '/');
+ if (ce_slash)
+ ce_len = ce_slash - ce_name;
+ else
+ ce_len = ce_namelen(ce) - pfxlen;
+ cmp = name_compare(p->path, p_len, ce_name, ce_len);
+ /*
+ * Exact match; if we have a directory we need to
+ * delay returning it.
+ */
+ if (!cmp)
+ return ce_slash ? -2 - pos : pos;
+ if (0 < cmp)
+ continue; /* keep looking */
+ /*
+ * ce_name sorts after p->path; could it be that we
+ * have files under p->path directory in the index?
+ * E.g. ce_name == "t-i", and p->path == "t"; we may
+ * have "t/a" in the index.
+ */
+ if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) &&
+ ce_name[p_len] < '/')
+ continue; /* keep looking */
+ break;
+ }
+ return -1;
+}
+
+static struct cache_entry *find_cache_entry(struct traverse_info *info,
+ const struct name_entry *p)
+{
+ int pos = find_cache_pos(info, p);
+ struct unpack_trees_options *o = info->data;
+
+ if (0 <= pos)
+ return o->src_index->cache[pos];
+ else
+ return NULL;
+}
+
+static void debug_path(struct traverse_info *info)
+{
+ if (info->prev) {
+ debug_path(info->prev);
+ if (*info->prev->name.path)
+ putchar('/');
+ }
+ printf("%s", info->name.path);
+}
+
+static void debug_name_entry(int i, struct name_entry *n)
+{
+ printf("ent#%d %06o %s\n", i,
+ n->path ? n->mode : 0,
+ n->path ? n->path : "(missing)");
+}
+
+static void debug_unpack_callback(int n,
+ unsigned long mask,
+ unsigned long dirmask,
+ struct name_entry *names,
+ struct traverse_info *info)
+{
+ int i;
+ printf("* unpack mask %lu, dirmask %lu, cnt %d ",
+ mask, dirmask, n);
+ debug_path(info);
+ putchar('\n');
+ for (i = 0; i < n; i++)
+ debug_name_entry(i, names + i);
+}
+
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
{
struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
while (!p->mode)
p++;
+ if (o->debug_unpack)
+ debug_unpack_callback(n, mask, dirmask, names, info);
+
/* Are we supposed to look at the index too? */
if (o->merge) {
- while (o->pos < o->src_index->cache_nr) {
- struct cache_entry *ce = o->src_index->cache[o->pos];
- int cmp = compare_entry(ce, info, p);
+ while (1) {
+ int cmp;
+ struct cache_entry *ce;
+
+ if (o->diff_index_cached)
+ ce = next_cache_entry(o);
+ else
+ ce = find_cache_entry(info, p);
+
+ if (!ce)
+ break;
+ cmp = compare_entry(ce, info, p);
if (cmp < 0) {
if (unpack_index_entry(ce, o) < 0)
return unpack_failed(o, NULL);
continue;
}
if (!cmp) {
- o->pos++;
if (ce_stage(ce)) {
/*
- * If we skip unmerged index entries, we'll skip this
- * entry *and* the tree entries associated with it!
+ * If we skip unmerged index
+ * entries, we'll skip this
+ * entry *and* the tree
+ * entries associated with it!
*/
if (o->skip_unmerged) {
- add_entry(o, ce, 0, 0);
+ add_same_unmerged(ce, o);
return mask;
}
}
if (unpack_nondirectories(n, mask, dirmask, src, names, info) < 0)
return -1;
+ if (src[0]) {
+ if (ce_stage(src[0]))
+ mark_ce_used_same_name(src[0], o);
+ else
+ mark_ce_used(src[0], o);
+ }
+
/* Now handle any directories.. */
if (dirmask) {
unsigned long conflicts = mask & ~dirmask;
matches = cache_tree_matches_traversal(o->src_index->cache_tree,
names, info);
/*
- * Everything under the name matches. Adjust o->pos to
- * skip the entire hierarchy.
+ * Everything under the name matches; skip the
+ * entire hierarchy. diff_index_cached codepath
+ * special cases D/F conflicts in such a way that
+ * it does not do any look-ahead, so this is safe.
*/
if (matches) {
- o->pos += matches;
+ o->cache_bottom += matches;
return mask;
}
}
memset(&o->result, 0, sizeof(o->result));
o->result.initialized = 1;
- if (o->src_index) {
- o->result.timestamp.sec = o->src_index->timestamp.sec;
- o->result.timestamp.nsec = o->src_index->timestamp.nsec;
- }
+ o->result.timestamp.sec = o->src_index->timestamp.sec;
+ o->result.timestamp.nsec = o->src_index->timestamp.nsec;
o->merge_size = len;
+ mark_all_ce_unused(o->src_index);
if (!dfc)
dfc = xcalloc(1, cache_entry_size(0));
info.fn = unpack_callback;
info.data = o;
- if (traverse_trees(len, t, &info) < 0) {
- ret = unpack_failed(o, NULL);
- goto done;
+ if (o->prefix) {
+ /*
+ * Unpack existing index entries that sort before the
+ * prefix the tree is spliced into. Note that o->merge
+ * is always true in this case.
+ */
+ while (1) {
+ struct cache_entry *ce = next_cache_entry(o);
+ if (!ce)
+ break;
+ if (ce_in_traverse_path(ce, &info))
+ break;
+ if (unpack_index_entry(ce, o) < 0)
+ goto return_failed;
+ }
}
+
+ if (traverse_trees(len, t, &info) < 0)
+ goto return_failed;
}
/* Any left-over entries in the index? */
if (o->merge) {
- while (o->pos < o->src_index->cache_nr) {
- struct cache_entry *ce = o->src_index->cache[o->pos];
- if (unpack_index_entry(ce, o) < 0) {
- ret = unpack_failed(o, NULL);
- goto done;
- }
+ while (1) {
+ struct cache_entry *ce = next_cache_entry(o);
+ if (!ce)
+ break;
+ if (unpack_index_entry(ce, o) < 0)
+ goto return_failed;
}
}
+ mark_all_ce_unused(o->src_index);
if (o->trivial_merges_only && o->nontrivial_merge) {
ret = unpack_failed(o, "Merge requires file-level merging");
free(el.excludes);
return ret;
+
+return_failed:
+ mark_all_ce_unused(o->src_index);
+ ret = unpack_failed(o, NULL);
+ goto done;
}
/* Here come the merge functions */
* in that directory.
*/
namelen = strlen(ce->name);
- for (i = o->pos; i < o->src_index->cache_nr; i++) {
+ for (i = locate_in_src_index(ce, o);
+ i < o->src_index->cache_nr;
+ i++) {
struct cache_entry *ce2 = o->src_index->cache[i];
int len = ce_namelen(ce2);
if (len < namelen ||
ce2->name[namelen] != '/')
break;
/*
- * ce2->name is an entry in the subdirectory.
+ * ce2->name is an entry in the subdirectory to be
+ * removed.
*/
if (!ce_stage(ce2)) {
if (verify_uptodate(ce2, o))
return -1;
add_entry(o, ce2, CE_REMOVE, 0);
+ mark_ce_used(ce2, o);
}
cnt++;
}
return 0;
if (!lstat(ce->name, &st)) {
- int ret;
int dtype = ce_to_dtype(ce);
struct cache_entry *result;
* files that are in "foo/" we would lose
* them.
*/
- ret = verify_clean_subdirectory(ce, action, o);
- if (ret < 0)
- return ret;
-
- /*
- * If this removed entries from the index,
- * what that means is:
- *
- * (1) the caller unpack_callback() saw path/foo
- * in the index, and it has not removed it because
- * it thinks it is handling 'path' as blob with
- * D/F conflict;
- * (2) we will return "ok, we placed a merged entry
- * in the index" which would cause o->pos to be
- * incremented by one;
- * (3) however, original o->pos now has 'path/foo'
- * marked with "to be removed".
- *
- * We need to increment it by the number of
- * deleted entries here.
- */
- o->pos += ret;
+ if (verify_clean_subdirectory(ce, action, o) < 0)
+ return -1;
return 0;
}
remote = NULL;
}
- /* First, if there's a #16 situation, note that to prevent #13
+ /*
+ * First, if there's a #16 situation, note that to prevent #13
* and #14.
*/
if (!same(remote, head)) {
}
}
- /* We start with cases where the index is allowed to match
+ /*
+ * We start with cases where the index is allowed to match
* something other than the head: #14(ALT) and #2ALT, where it
* is permitted to match the result instead.
*/
if (!head && !remote && any_anc_missing)
return 0;
- /* Under the new "aggressive" rule, we resolve mostly trivial
+ /*
+ * Under the "aggressive" rule, we resolve mostly trivial
* cases that we historically had git-merge-one-file resolve.
*/
if (o->aggressive) {
- int head_deleted = !head && !df_conflict_head;
- int remote_deleted = !remote && !df_conflict_remote;
+ int head_deleted = !head;
+ int remote_deleted = !remote;
struct cache_entry *ce = NULL;
if (index)
skip_unmerged,
initial_checkout,
diff_index_cached,
+ debug_unpack,
skip_sparse_checkout,
gently;
const char *prefix;
- int pos;
+ int cache_bottom;
struct dir_struct *dir;
merge_fn_t fn;
struct unpack_trees_error_msgs msgs;