diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Documentation/callouts.xsl | 14 | ||||
-rw-r--r-- | Documentation/config.txt | 12 | ||||
-rw-r--r-- | Documentation/git-merge-index.txt | 4 | ||||
-rw-r--r-- | Documentation/git-push.txt | 15 | ||||
-rw-r--r-- | Documentation/git-svn.txt | 6 | ||||
-rw-r--r-- | INSTALL | 9 | ||||
-rw-r--r-- | Makefile | 31 | ||||
-rw-r--r-- | builtin-log.c | 2 | ||||
-rw-r--r-- | builtin-merge-file.c | 79 | ||||
-rw-r--r-- | builtin-push.c | 35 | ||||
-rw-r--r-- | builtin.h | 1 | ||||
-rw-r--r-- | config.c | 2 | ||||
-rwxr-xr-x | contrib/completion/git-completion.bash | 3 | ||||
-rw-r--r-- | diff.c | 4 | ||||
-rw-r--r-- | fetch.c | 15 | ||||
-rwxr-xr-x | git-cvsserver.perl | 2 | ||||
-rwxr-xr-x | git-merge.sh | 9 | ||||
-rwxr-xr-x | git-rerere.perl | 2 | ||||
-rwxr-xr-x | git-svn.perl | 225 | ||||
-rw-r--r-- | git.c | 1 | ||||
-rw-r--r-- | git.spec.in | 2 | ||||
-rw-r--r-- | merge-file.c | 75 | ||||
-rw-r--r-- | merge-recursive.c | 144 | ||||
-rw-r--r-- | refs.c | 2 | ||||
-rw-r--r-- | send-pack.c | 21 | ||||
-rwxr-xr-x | t/t0000-basic.sh | 14 | ||||
-rw-r--r-- | t/t6023-merge-file.sh | 116 | ||||
-rwxr-xr-x | t/t6023-merge-rename-nocruft.sh | 97 | ||||
-rw-r--r-- | t/t6024-recursive-merge.sh | 80 | ||||
-rw-r--r-- | wt-status.c | 4 | ||||
-rw-r--r-- | xdiff/xdiff.h | 7 | ||||
-rw-r--r-- | xdiff/xdiffi.c | 3 | ||||
-rw-r--r-- | xdiff/xdiffi.h | 1 | ||||
-rw-r--r-- | xdiff/xmerge.c | 419 |
35 files changed, 1207 insertions, 250 deletions
diff --git a/.gitignore b/.gitignore index 7f2cd55088..d706dd92c6 100644 --- a/.gitignore +++ b/.gitignore @@ -60,6 +60,7 @@ git-mailsplit git-merge git-merge-base git-merge-index +git-merge-file git-merge-tree git-merge-octopus git-merge-one-file diff --git a/Documentation/callouts.xsl b/Documentation/callouts.xsl index ad03755d8f..6a361a2136 100644 --- a/Documentation/callouts.xsl +++ b/Documentation/callouts.xsl @@ -13,4 +13,18 @@ <xsl:apply-templates/> <xsl:text>.br </xsl:text> </xsl:template> + +<!-- sorry, this is not about callouts, but attempts to work around + spurious .sp at the tail of the line docbook stylesheets seem to add --> +<xsl:template match="simpara"> + <xsl:variable name="content"> + <xsl:apply-templates/> + </xsl:variable> + <xsl:value-of select="normalize-space($content)"/> + <xsl:if test="not(ancestor::authorblurb) and + not(ancestor::personblurb)"> + <xsl:text> </xsl:text> + </xsl:if> +</xsl:template> + </xsl:stylesheet> diff --git a/Documentation/config.txt b/Documentation/config.txt index 21ec55797b..f5a552ee87 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -137,16 +137,16 @@ branch.<name>.merge:: this option, `git pull` defaults to merge the first refspec fetched. Specify multiple values to get an octopus merge. -pager.color:: +color.pager:: A boolean to enable/disable colored output when the pager is in use (default is true). -diff.color:: +color.diff:: When true (or `always`), always use colors in patch. When false (or `never`), never. When set to `auto`, use colors only when the output is to the terminal. -diff.color.<slot>:: +color.diff.<slot>:: Use customized color for diff colorization. `<slot>` specifies which part of the patch to use the specified color, and is one of `plain` (context text), `meta` @@ -271,19 +271,19 @@ showbranch.default:: The default set of branches for gitlink:git-show-branch[1]. See gitlink:git-show-branch[1]. -status.color:: +color.status:: A boolean to enable/disable color in the output of gitlink:git-status[1]. May be set to `true` (or `always`), `false` (or `never`) or `auto`, in which case colors are used only when the output is to a terminal. Defaults to false. -status.color.<slot>:: +color.status.<slot>:: Use customized color for status colorization. `<slot>` is one of `header` (the header text of the status message), `updated` (files which are updated but not committed), `changed` (files which are changed but not updated in the index), or `untracked` (files which are not tracked by git). The values of - these variables may be specified as in diff.color.<slot>. + these variables may be specified as in color.diff.<slot>. tar.umask:: By default, gitlink:git-tar-tree[1] sets file and directories modes diff --git a/Documentation/git-merge-index.txt b/Documentation/git-merge-index.txt index 6cd0601082..0cf505ea84 100644 --- a/Documentation/git-merge-index.txt +++ b/Documentation/git-merge-index.txt @@ -40,8 +40,8 @@ If "git-merge-index" is called with multiple <file>s (or -a) then it processes them in turn only stopping if merge returns a non-zero exit code. -Typically this is run with the a script calling the merge command from -the RCS package. +Typically this is run with the a script calling git's imitation of +the merge command from the RCS package. A sample script called "git-merge-one-file" is included in the distribution. diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index d4ae99fa53..197f4b512f 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -49,12 +49,14 @@ corresponding remotes file---see below), then all the refs that exist both on the local side and on the remote side are updated. + -Some short-cut notations are also supported. +`tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. + -* `tag <tag>` means the same as `refs/tags/<tag>:refs/tags/<tag>`. -* A parameter <ref> without a colon is equivalent to - <ref>`:`<ref>, hence updates <ref> in the destination from <ref> - in the source. +A parameter <ref> without a colon is equivalent to +<ref>`:`<ref>, hence updates <ref> in the destination from <ref> +in the source. ++ +Pushing an empty <src> allows you to delete the <dst> ref from +the remote repository. \--all:: Instead of naming each ref to push, specifies that all @@ -75,7 +77,8 @@ include::urls.txt[] Author ------ -Written by Junio C Hamano <junkio@cox.net> +Written by Junio C Hamano <junkio@cox.net>, later rewritten in C +by Linus Torvalds <torvalds@osdl.org> Documentation -------------- diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index a45067e164..c589a98630 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -57,11 +57,13 @@ See '<<fetch-args,Additional Fetch Arguments>>' if you are interested in manually joining branches on commit. 'dcommit':: - Commit all diffs from the current HEAD directly to the SVN + Commit all diffs from a specified head directly to the SVN repository, and then rebase or reset (depending on whether or - not there is a diff between SVN and HEAD). It is recommended + not there is a diff between SVN and head). It is recommended that you run git-svn fetch and rebase (not pull) your commits against the latest changes in the SVN repository. + An optional command-line argument may be specified as an + alternative to HEAD. This is advantageous over 'commit' (below) because it produces cleaner, more linear history. @@ -82,15 +82,6 @@ Issues of note: do that even if it wasn't for git. There's no point in living in the dark ages any more. - - "merge", the standard UNIX three-way merge program. It usually - comes with the "rcs" package on most Linux distributions, so if - you have a developer install you probably have it already, but a - "graphical user desktop" install might have left it out. - - You'll only need the merge program if you do development using - git, and if you only use git to track other peoples work you'll - never notice the lack of it. - - "wish", the Tcl/Tk windowing shell is used in gitk to show the history graphically @@ -279,6 +279,7 @@ BUILTIN_OBJS = \ builtin-ls-tree.o \ builtin-mailinfo.o \ builtin-mailsplit.o \ + builtin-merge-file.o \ builtin-mv.o \ builtin-name-rev.o \ builtin-pack-objects.o \ @@ -326,18 +327,6 @@ ifeq ($(uname_S),Darwin) NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease NO_STRLCPY = YesPlease - ifndef NO_FINK - ifeq ($(shell test -d /sw/lib && echo y),y) - BASIC_CFLAGS += -I/sw/include - BASIC_LDFLAGS += -L/sw/lib - endif - endif - ifndef NO_DARWIN_PORTS - ifeq ($(shell test -d /opt/local/lib && echo y),y) - BASIC_CFLAGS += -I/opt/local/include - BASIC_LDFLAGS += -L/opt/local/lib - endif - endif endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@ -415,6 +404,21 @@ endif -include config.mak.autogen -include config.mak +ifeq ($(uname_S),Darwin) + ifndef NO_FINK + ifeq ($(shell test -d /sw/lib && echo y),y) + BASIC_CFLAGS += -I/sw/include + BASIC_LDFLAGS += -L/sw/lib + endif + endif + ifndef NO_DARWIN_PORTS + ifeq ($(shell test -d /opt/local/lib && echo y),y) + BASIC_CFLAGS += -I/opt/local/include + BASIC_LDFLAGS += -L/opt/local/lib + endif + endif +endif + ifndef NO_CURL ifdef CURLDIR # This is still problematic -- gcc does not always want -R. @@ -734,7 +738,8 @@ $(DIFF_OBJS): diffcore.h $(LIB_FILE): $(LIB_OBJS) rm -f $@ && $(AR) rcs $@ $(LIB_OBJS) -XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o +XDIFF_OBJS=xdiff/xdiffi.o xdiff/xprepare.o xdiff/xutils.o xdiff/xemit.o \ + xdiff/xmerge.o $(XDIFF_OBJS): xdiff/xinclude.h xdiff/xmacros.h xdiff/xdiff.h xdiff/xtypes.h \ xdiff/xutils.h xdiff/xprepare.h xdiff/xdiffi.h xdiff/xemit.h diff --git a/builtin-log.c b/builtin-log.c index 7acf5d3b0c..6821a08442 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -118,7 +118,7 @@ static int git_format_config(const char *var, const char *value) strcat(extra_headers, value); return 0; } - if (!strcmp(var, "diff.color")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { return 0; } return git_log_config(var, value); diff --git a/builtin-merge-file.c b/builtin-merge-file.c new file mode 100644 index 0000000000..6c4c3a3513 --- /dev/null +++ b/builtin-merge-file.c @@ -0,0 +1,79 @@ +#include "cache.h" +#include "xdiff/xdiff.h" + +static const char merge_file_usage[] = +"git merge-file [-p | --stdout] [-q | --quiet] [-L name1 [-L orig [-L name2]]] file1 orig_file file2"; + +static int read_file(mmfile_t *ptr, const char *filename) +{ + struct stat st; + FILE *f; + + if (stat(filename, &st)) + return error("Could not stat %s", filename); + if ((f = fopen(filename, "rb")) == NULL) + return error("Could not open %s", filename); + ptr->ptr = xmalloc(st.st_size); + if (fread(ptr->ptr, st.st_size, 1, f) != 1) + return error("Could not read %s", filename); + fclose(f); + ptr->size = st.st_size; + return 0; +} + +int cmd_merge_file(int argc, char **argv, char **envp) +{ + char *names[3]; + mmfile_t mmfs[3]; + mmbuffer_t result = {NULL, 0}; + xpparam_t xpp = {XDF_NEED_MINIMAL}; + int ret = 0, i = 0, to_stdout = 0; + + while (argc > 4) { + if (!strcmp(argv[1], "-L") && i < 3) { + names[i++] = argv[2]; + argc--; + argv++; + } else if (!strcmp(argv[1], "-p") || + !strcmp(argv[1], "--stdout")) + to_stdout = 1; + else if (!strcmp(argv[1], "-q") || + !strcmp(argv[1], "--quiet")) + freopen("/dev/null", "w", stderr); + else + usage(merge_file_usage); + argc--; + argv++; + } + + if (argc != 4) + usage(merge_file_usage); + + for (; i < 3; i++) + names[i] = argv[i + 1]; + + for (i = 0; i < 3; i++) + if (read_file(mmfs + i, argv[i + 1])) + return -1; + + ret = xdl_merge(mmfs + 1, mmfs + 0, names[0], mmfs + 2, names[2], + &xpp, XDL_MERGE_ZEALOUS, &result); + + for (i = 0; i < 3; i++) + free(mmfs[i].ptr); + + if (ret >= 0) { + char *filename = argv[1]; + FILE *f = to_stdout ? stdout : fopen(filename, "wb"); + + if (!f) + ret = error("Could not open %s for writing", filename); + else if (fwrite(result.ptr, result.size, 1, f) != 1) + ret = error("Could not write to %s", filename); + else if (fclose(f)) + ret = error("Could not close %s", filename); + free(result.ptr); + } + + return ret; +} diff --git a/builtin-push.c b/builtin-push.c index d23974e708..b7412e8293 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -57,11 +57,36 @@ static void expand_refspecs(void) static void set_refspecs(const char **refs, int nr) { if (nr) { - size_t bytes = nr * sizeof(char *); - - refspec = xrealloc(refspec, bytes); - memcpy(refspec, refs, bytes); - refspec_nr = nr; + int pass; + for (pass = 0; pass < 2; pass++) { + /* pass 0 counts and allocates, pass 1 fills */ + int i, cnt; + for (i = cnt = 0; i < nr; i++) { + if (!strcmp("tag", refs[i])) { + int len; + char *tag; + if (nr <= ++i) + die("tag <tag> shorthand without <tag>"); + if (pass) { + len = strlen(refs[i]) + 11; + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + strcat(tag, refs[i]); + refspec[cnt] = tag; + } + cnt++; + continue; + } + if (pass) + refspec[cnt] = refs[i]; + cnt++; + } + if (!pass) { + size_t bytes = cnt * sizeof(char *); + refspec_nr = cnt; + refspec = xrealloc(refspec, bytes); + } + } } expand_refspecs(); } @@ -42,6 +42,7 @@ extern int cmd_ls_files(int argc, const char **argv, const char *prefix); extern int cmd_ls_tree(int argc, const char **argv, const char *prefix); extern int cmd_mailinfo(int argc, const char **argv, const char *prefix); extern int cmd_mailsplit(int argc, const char **argv, const char *prefix); +extern int cmd_merge_file(int argc, const char **argv, const char *prefix); extern int cmd_mv(int argc, const char **argv, const char *prefix); extern int cmd_name_rev(int argc, const char **argv, const char *prefix); extern int cmd_pack_objects(int argc, const char **argv, const char *prefix); @@ -314,7 +314,7 @@ int git_default_config(const char *var, const char *value) return 0; } - if (!strcmp(var, "pager.color")) { + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; } diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 447ec20467..9c4d23a23c 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -712,10 +712,13 @@ _git_repo_config () core.legacyHeaders i18n.commitEncoding diff.color + color.diff diff.renameLimit diff.renames pager.color + color.pager status.color + color.status log.showroot show.difftree showbranch.default @@ -60,7 +60,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_rename_limit_default = git_config_int(var, value); return 0; } - if (!strcmp(var, "diff.color")) { + if (!strcmp(var, "diff.color") || !strcmp(var, "color.diff")) { diff_use_color_default = git_config_colorbool(var, value); return 0; } @@ -74,7 +74,7 @@ int git_diff_ui_config(const char *var, const char *value) diff_detect_rename_default = DIFF_DETECT_RENAME; return 0; } - if (!strncmp(var, "diff.color.", 11)) { + if (!strncmp(var, "diff.color.", 11) || !strncmp(var, "color.diff.", 11)) { int slot = parse_diff_color_slot(var, 11); color_parse(value, var, diff_colors[slot]); return 0; @@ -22,14 +22,15 @@ void pull_say(const char *fmt, const char *hex) fprintf(stderr, fmt, hex); } -static void report_missing(const char *what, const unsigned char *missing) +static void report_missing(const struct object *obj) { char missing_hex[41]; - - strcpy(missing_hex, sha1_to_hex(missing));; - fprintf(stderr, - "Cannot obtain needed %s %s\nwhile processing commit %s.\n", - what, missing_hex, sha1_to_hex(current_commit_sha1)); + strcpy(missing_hex, sha1_to_hex(obj->sha1));; + fprintf(stderr, "Cannot obtain needed %s %s\n", + obj->type ? typename(obj->type): "object", missing_hex); + if (!is_null_sha1(current_commit_sha1)) + fprintf(stderr, "while processing commit %s.\n", + sha1_to_hex(current_commit_sha1)); } static int process(struct object *obj); @@ -177,7 +178,7 @@ static int loop(void) */ if (! (obj->flags & TO_SCAN)) { if (fetch(obj->sha1)) { - report_missing(typename(obj->type), obj->sha1); + report_missing(obj); return -1; } } diff --git a/git-cvsserver.perl b/git-cvsserver.perl index 197014d9e6..2a8447e253 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -946,7 +946,7 @@ sub req_update $log->debug("Temporary directory for merge is $dir"); - my $return = system("merge", $file_local, $file_old, $file_new); + my $return = system("git merge-file", $file_local, $file_old, $file_new); $return >>= 8; if ( $return == 0 ) diff --git a/git-merge.sh b/git-merge.sh index a948878b91..2f3d936b9c 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -400,7 +400,14 @@ fi case "$best_strategy" in '') restorestate - echo >&2 "No merge strategy handled the merge." + case "$use_strategies" in + ?*' '?*) + echo >&2 "No merge strategy handled the merge." + ;; + *) + echo >&2 "Merge with strategy $use_strategies failed." + ;; + esac exit 2 ;; "$wt_strategy") diff --git a/git-rerere.perl b/git-rerere.perl index d3664ff491..2e8dbbd4ea 100755 --- a/git-rerere.perl +++ b/git-rerere.perl @@ -154,7 +154,7 @@ sub find_conflict { sub merge { my ($name, $path) = @_; record_preimage($path, "$rr_dir/$name/thisimage"); - unless (system('merge', map { "$rr_dir/$name/${_}image" } + unless (system('git merge-file', map { "$rr_dir/$name/${_}image" } qw(this pre post))) { my $in; open $in, "<$rr_dir/$name/thisimage" or diff --git a/git-svn.perl b/git-svn.perl index 1f8a3b0e07..15254e4795 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -21,7 +21,17 @@ $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; $| = 1; # unbuffer STDOUT -sub fatal (@) { print STDERR $@; exit 1 } +# properties that we do not log: +my %SKIP = ( 'svn:wc:ra_dav:version-url' => 1, + 'svn:special' => 1, + 'svn:executable' => 1, + 'svn:entry:committed-rev' => 1, + 'svn:entry:last-author' => 1, + 'svn:entry:uuid' => 1, + 'svn:entry:committed-date' => 1, +); + +sub fatal (@) { print STDERR @_; exit 1 } # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load @@ -594,8 +604,9 @@ sub commit_lib { } sub dcommit { + my $head = shift || 'HEAD'; my $gs = "refs/remotes/$GIT_SVN"; - chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..HEAD")); + chomp(my @refs = safe_qx(qw/git-rev-list --no-merges/, "$gs..$head")); my $last_rev; foreach my $d (reverse @refs) { if (quiet_run('git-rev-parse','--verify',"$d~1") != 0) { @@ -622,16 +633,16 @@ sub dcommit { } return if $_dry_run; fetch(); - my @diff = safe_qx(qw/git-diff-tree HEAD/, $gs); + my @diff = safe_qx('git-diff-tree', $head, $gs); my @finish; if (@diff) { @finish = qw/rebase/; push @finish, qw/--merge/ if $_merge; push @finish, "--strategy=$_strategy" if $_strategy; - print STDERR "W: HEAD and $gs differ, using @finish:\n", @diff; + print STDERR "W: $head and $gs differ, using @finish:\n", @diff; } else { - print "No changes between current HEAD and $gs\n", - "Hard resetting to the latest $gs\n"; + print "No changes between current $head and $gs\n", + "Resetting to the latest $gs\n"; @finish = qw/reset --mixed/; } sys('git', @finish, $gs); @@ -2016,9 +2027,17 @@ sub git_commit { # just in case we clobber the existing ref, we still want that ref # as our parent: - if (my $cur = eval { file_to_s("$GIT_DIR/refs/remotes/$GIT_SVN") }) { + open my $null, '>', '/dev/null' or croak $!; + open my $stderr, '>&', \*STDERR or croak $!; + open STDERR, '>&', $null or croak $!; + if (my $cur = eval { safe_qx('git-rev-parse', + "refs/remotes/$GIT_SVN^0") }) { + chomp $cur; push @tmp_parents, $cur; } + open STDERR, '>&', $stderr or croak $!; + close $stderr or croak $!; + close $null or croak $!; if (exists $tree_map{$tree}) { foreach my $p (@{$tree_map{$tree}}) { @@ -2902,7 +2921,7 @@ sub libsvn_dup_ra { } sub libsvn_get_file { - my ($gui, $f, $rev, $chg) = @_; + my ($gui, $f, $rev, $chg, $untracked) = @_; $f =~ s#^/##; print "\t$chg\t$f\n" unless $_q; @@ -2940,11 +2959,25 @@ sub libsvn_get_file { waitpid $pid, 0; $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; } + %{$untracked->{file_prop}->{$f}} = %$props; print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!; } +sub uri_encode { + my ($f) = @_; + $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; + $f +} + +sub uri_decode { + my ($f) = @_; + $f =~ tr/+/ /; + $f =~ s/%([A-F0-9]{2})/chr hex($1)/ge; + $f +} + sub libsvn_log_entry { - my ($rev, $author, $date, $msg, $parents) = @_; + my ($rev, $author, $date, $msg, $parents, $untracked) = @_; my ($Y,$m,$d,$H,$M,$S) = ($date =~ /^(\d{4})\-(\d\d)\-(\d\d)T (\d\d)\:(\d\d)\:(\d\d).\d+Z$/x) or die "Unable to parse date: $date\n"; @@ -2952,8 +2985,65 @@ sub libsvn_log_entry { die "Author: $author not defined in $_authors file\n"; } $msg = '' if ($rev == 0 && !defined $msg); - return { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", - author => $author, msg => $msg."\n", parents => $parents || [] } + + open my $un, '>>', "$GIT_SVN_DIR/unhandled.log" or croak $!; + my $h; + print $un "r$rev\n" or croak $!; + $h = $untracked->{empty}; + foreach (sort keys %$h) { + my $act = $h->{$_} ? '+empty_dir' : '-empty_dir'; + print $un " $act: ", uri_encode($_), "\n" or croak $!; + warn "W: $act: $_\n"; + } + foreach my $t (qw/dir_prop file_prop/) { + $h = $untracked->{$t} or next; + foreach my $path (sort keys %$h) { + my $ppath = $path eq '' ? '.' : $path; + foreach my $prop (sort keys %{$h->{$path}}) { + next if $SKIP{$prop}; + my $v = $h->{$path}->{$prop}; + if (defined $v) { + print $un " +$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), ' ', + uri_encode($v), "\n" + or croak $!; + } else { + print $un " -$t: ", + uri_encode($ppath), ' ', + uri_encode($prop), "\n" + or croak $!; + } + } + } + } + foreach my $t (qw/absent_file absent_directory/) { + $h = $untracked->{$t} or next; + foreach my $parent (sort keys %$h) { + foreach my $path (sort @{$h->{$parent}}) { + print $un " $t: ", + uri_encode("$parent/$path"), "\n" + or croak $!; + warn "W: $t: $parent/$path ", + "Insufficient permissions?\n"; + } + } + } + + # revprops (make this optional? it's an extra network trip...) + my $pool = SVN::Pool->new; + my $rp = $SVN->rev_proplist($rev, $pool); + foreach (sort keys %$rp) { + next if /^svn:(?:author|date|log)$/; + print $un " rev_prop: ", uri_encode($_), ' ', + uri_encode($rp->{$_}), "\n"; + } + $pool->clear; + close $un or croak $!; + + { revision => $rev, date => "+0000 $Y-$m-$d $H:$M:$S", + author => $author, msg => $msg."\n", parents => $parents || [], + revprops => $rp } } sub process_rm { @@ -2972,9 +3062,11 @@ sub process_rm { } print "\tD\t$f/\n" unless $q; close $ls or croak $?; + return $SVN::Node::dir; } else { print $gui '0 ',0 x 40,"\t",$f,"\0" or croak $!; print "\tD\t$f\n" unless $q; + return $SVN::Node::file; } } @@ -2995,13 +3087,14 @@ sub libsvn_fetch_delta { unless ($ed->{git_commit_ok}) { die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); } sub libsvn_fetch_full { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; open my $gui, '| git-update-index -z --index-info' or croak $!; my %amr; + my $ut = { empty => {}, dir_prop => {}, file_prop => {} }; my $p = $SVN->{svn_path}; foreach my $f (keys %$paths) { my $m = $paths->{$f}->action(); @@ -3012,8 +3105,11 @@ sub libsvn_fetch_full { $f =~ s#^/##; } if ($m =~ /^[DR]$/) { - process_rm($gui, $last_commit, $f, $_q); - next if $m eq 'D'; + my $t = process_rm($gui, $last_commit, $f, $_q); + if ($m eq 'D') { + $ut->{empty}->{$f} = 0 if $t == $SVN::Node::dir; + next; + } # 'R' can be file replacements, too, right? } my $pool = SVN::Pool->new; @@ -3026,18 +3122,32 @@ sub libsvn_fetch_full { } } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) { my @traversed = (); - libsvn_traverse($gui, '', $f, $rev, \@traversed); - foreach (@traversed) { - $amr{$_} = $m; + libsvn_traverse($gui, '', $f, $rev, \@traversed, $ut); + if (@traversed) { + foreach (@traversed) { + $amr{$_} = $m; + } + } else { + my ($dir, $file) = ($f =~ m#^(.*?)/?([^/]+)$#); + delete $ut->{empty}->{$dir}; + $ut->{empty}->{$f} = 1; } } $pool->clear; } foreach (keys %amr) { - libsvn_get_file($gui, $_, $rev, $amr{$_}); + libsvn_get_file($gui, $_, $rev, $amr{$_}, $ut); + my ($d) = ($_ =~ m#^(.*?)/?(?:[^/]+)$#); + delete $ut->{empty}->{$d}; + } + unless (exists $ut->{dir_prop}->{''}) { + my $pool = SVN::Pool->new; + my (undef, undef, $props) = $SVN->get_dir('', $rev, $pool); + %{$ut->{dir_prop}->{''}} = %$props; + $pool->clear; } close $gui or croak $?; - return libsvn_log_entry($rev, $author, $date, $msg, [$last_commit]); + libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut); } sub svn_grab_base_rev { @@ -3098,25 +3208,38 @@ sub libsvn_parse_revision { } sub libsvn_traverse { - my ($gui, $pfx, $path, $rev, $files) = @_; + my ($gui, $pfx, $path, $rev, $files, $untracked) = @_; my $cwd = length $pfx ? "$pfx/$path" : $path; my $pool = SVN::Pool->new; $cwd =~ s#^\Q$SVN->{svn_path}\E##; + my $nr = 0; my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); + %{$untracked->{dir_prop}->{$cwd}} = %$props; foreach my $d (keys %$dirent) { my $t = $dirent->{$d}->kind; if ($t == $SVN::Node::dir) { - libsvn_traverse($gui, $cwd, $d, $rev, $files); + my $i = libsvn_traverse($gui, $cwd, $d, $rev, + $files, $untracked); + if ($i) { + $nr += $i; + } else { + $untracked->{empty}->{"$cwd/$d"} = 1; + } } elsif ($t == $SVN::Node::file) { + $nr++; my $file = "$cwd/$d"; if (defined $files) { push @$files, $file; } else { - libsvn_get_file($gui, $file, $rev, 'A'); + libsvn_get_file($gui, $file, $rev, 'A', + $untracked); + my ($dir) = ($file =~ m#^(.*?)/?(?:[^/]+)$#); + delete $untracked->{empty}->{$dir}; } } } $pool->clear; + $nr; } sub libsvn_traverse_ignore { @@ -3255,6 +3378,7 @@ sub libsvn_new_tree { return $log_entry; } my ($paths, $rev, $author, $date, $msg) = @_; + my $ut; if ($_xfer_delta) { my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({q => $_q}); @@ -3266,12 +3390,14 @@ sub libsvn_new_tree { unless ($ed->{git_commit_ok}) { die "SVN connection failed somewhere...\n"; } + $ut = $ed; } else { + $ut = { empty => {}, dir_prop => {}, file_prop => {} }; open my $gui, '| git-update-index -z --index-info' or croak $!; - libsvn_traverse($gui, '', $SVN->{svn_path}, $rev); + libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut); close $gui or croak $?; } - return libsvn_log_entry($rev, $author, $date, $msg); + libsvn_log_entry($rev, $author, $date, $msg, [], $ut); } sub find_graft_path_commit { @@ -3456,13 +3582,28 @@ sub new { $self->{gui} = $gui; $self->{c} = $git_svn->{c} if exists $git_svn->{c}; $self->{q} = $git_svn->{q}; + $self->{empty} = {}; + $self->{dir_prop} = {}; + $self->{file_prop} = {}; + $self->{absent_dir} = {}; + $self->{absent_file} = {}; require Digest::MD5; $self; } +sub open_root { + { path => '' }; +} + +sub open_directory { + my ($self, $path, $pb, $rev) = @_; + { path => $path }; +} + sub delete_entry { my ($self, $path, $rev, $pb) = @_; - process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + my $t = process_rm($self->{gui}, $self->{c}, $path, $self->{q}); + $self->{empty}->{$path} = 0 if $t == $SVN::Node::dir; undef; } @@ -3479,10 +3620,41 @@ sub open_file { sub add_file { my ($self, $path, $pb, $cp_path, $cp_rev) = @_; + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; { path => $path, mode_a => 100644, mode_b => 100644, pool => SVN::Pool->new, action => 'A' }; } +sub add_directory { + my ($self, $path, $cp_path, $cp_rev) = @_; + my ($dir, $file) = ($path =~ m#^(.*?)/?([^/]+)$#); + delete $self->{empty}->{$dir}; + $self->{empty}->{$path} = 1; + { path => $path }; +} + +sub change_dir_prop { + my ($self, $db, $prop, $value) = @_; + $self->{dir_prop}->{$db->{path}} ||= {}; + $self->{dir_prop}->{$db->{path}}->{$prop} = $value; + undef; +} + +sub absent_directory { + my ($self, $path, $pb) = @_; + $self->{absent_dir}->{$pb->{path}} ||= []; + push @{$self->{absent_dir}->{$pb->{path}}}, $path; + undef; +} + +sub absent_file { + my ($self, $path, $pb) = @_; + $self->{absent_file}->{$pb->{path}} ||= []; + push @{$self->{absent_file}->{$pb->{path}}}, $path; + undef; +} + sub change_file_prop { my ($self, $fb, $prop, $value) = @_; if ($prop eq 'svn:executable') { @@ -3491,6 +3663,9 @@ sub change_file_prop { } } elsif ($prop eq 'svn:special') { $fb->{mode_b} = defined $value ? 120000 : 100644; + } else { + $self->{file_prop}->{$fb->{path}} ||= {}; + $self->{file_prop}->{$fb->{path}}->{$prop} = $value; } undef; } @@ -247,6 +247,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp) { "ls-tree", cmd_ls_tree, RUN_SETUP }, { "mailinfo", cmd_mailinfo }, { "mailsplit", cmd_mailsplit }, + { "merge-file", cmd_merge_file }, { "mv", cmd_mv, RUN_SETUP }, { "name-rev", cmd_name_rev, RUN_SETUP }, { "pack-objects", cmd_pack_objects, RUN_SETUP }, diff --git a/git.spec.in b/git.spec.in index f2374b7331..fb95e37594 100644 --- a/git.spec.in +++ b/git.spec.in @@ -24,7 +24,7 @@ This is a dummy package which brings in all subpackages. %package core Summary: Core git tools Group: Development/Tools -Requires: zlib >= 1.2, rsync, rcs, curl, less, openssh-clients, expat +Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat %description core This is a stupid (but extremely fast) directory content manager. It doesn't do a whole lot, but what it _does_ do is track directory diff --git a/merge-file.c b/merge-file.c index fc9b148993..69dc1ebbf7 100644 --- a/merge-file.c +++ b/merge-file.c @@ -3,52 +3,6 @@ #include "xdiff-interface.h" #include "blob.h" -static void rm_temp_file(const char *filename) -{ - unlink(filename); - free((void *)filename); -} - -static const char *write_temp_file(mmfile_t *f) -{ - int fd; - const char *tmp = getenv("TMPDIR"); - char *filename; - - if (!tmp) - tmp = "/tmp"; - filename = mkpath("%s/%s", tmp, "git-tmp-XXXXXX"); - fd = mkstemp(filename); - if (fd < 0) - return NULL; - filename = xstrdup(filename); - if (f->size != xwrite(fd, f->ptr, f->size)) { - rm_temp_file(filename); - return NULL; - } - close(fd); - return filename; -} - -static void *read_temp_file(const char *filename, unsigned long *size) -{ - struct stat st; - char *buf = NULL; - int fd = open(filename, O_RDONLY); - if (fd < 0) - return NULL; - if (!fstat(fd, &st)) { - *size = st.st_size; - buf = xmalloc(st.st_size); - if (st.st_size != xread(fd, buf, st.st_size)) { - free(buf); - buf = NULL; - } - } - close(fd); - return buf; -} - static int fill_mmfile_blob(mmfile_t *f, struct blob *obj) { void *buf; @@ -72,22 +26,19 @@ static void free_mmfile(mmfile_t *f) static void *three_way_filemerge(mmfile_t *base, mmfile_t *our, mmfile_t *their, unsigned long *size) { - void *res; - const char *t1, *t2, *t3; - - t1 = write_temp_file(base); - t2 = write_temp_file(our); - t3 = write_temp_file(their); - res = NULL; - if (t1 && t2 && t3) { - int code = run_command("merge", t2, t1, t3, NULL); - if (!code || code == -1) - res = read_temp_file(t2, size); - } - rm_temp_file(t1); - rm_temp_file(t2); - rm_temp_file(t3); - return res; + mmbuffer_t res; + xpparam_t xpp; + int merge_status; + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(base, our, ".our", their, ".their", + &xpp, XDL_MERGE_ZEALOUS, &res); + + if (merge_status < 0) + return NULL; + + *size = res.size; + return res.ptr; } static int common_outf(void *priv_, mmbuffer_t *mb, int nbuf) diff --git a/merge-recursive.c b/merge-recursive.c index 32e186c15e..6dd6e2e5af 100644 --- a/merge-recursive.c +++ b/merge-recursive.c @@ -21,6 +21,7 @@ #include "tag.h" #include "unpack-trees.h" #include "path-list.h" +#include "xdiff-interface.h" /* * A virtual commit has @@ -604,24 +605,21 @@ struct merge_file_info merge:1; }; -static char *git_unpack_file(const unsigned char *sha1, char *path) +static void fill_mm(const unsigned char *sha1, mmfile_t *mm) { - void *buf; - char type[20]; unsigned long size; - int fd; + char type[20]; - buf = read_sha1_file(sha1, type, &size); - if (!buf || strcmp(type, blob_type)) - die("unable to read blob object %s", sha1_to_hex(sha1)); + if (!hashcmp(sha1, null_sha1)) { + mm->ptr = xstrdup(""); + mm->size = 0; + return; + } - strcpy(path, ".merge_file_XXXXXX"); - fd = mkstemp(path); - if (fd < 0) - die("unable to create temp-file"); - flush_buffer(fd, buf, size); - close(fd); - return path; + mm->ptr = read_sha1_file(sha1, type, &size); + if (!mm->ptr || strcmp(type, blob_type)) + die("unable to read blob object %s", sha1_to_hex(sha1)); + mm->size = size; } static struct merge_file_info merge_file(struct diff_filespec *o, @@ -652,49 +650,41 @@ static struct merge_file_info merge_file(struct diff_filespec *o, else if (sha_eq(b->sha1, o->sha1)) hashcpy(result.sha, a->sha1); else if (S_ISREG(a->mode)) { - int code = 1, fd; - struct stat st; - char orig[PATH_MAX]; - char src1[PATH_MAX]; - char src2[PATH_MAX]; - const char *argv[] = { - "merge", "-L", NULL, "-L", NULL, "-L", NULL, - NULL, NULL, NULL, - NULL - }; - char *la, *lb, *lo; - - git_unpack_file(o->sha1, orig); - git_unpack_file(a->sha1, src1); - git_unpack_file(b->sha1, src2); - - argv[2] = la = xstrdup(mkpath("%s/%s", branch1, a->path)); - argv[6] = lb = xstrdup(mkpath("%s/%s", branch2, b->path)); - argv[4] = lo = xstrdup(mkpath("orig/%s", o->path)); - argv[7] = src1; - argv[8] = orig; - argv[9] = src2, - - code = run_command_v(10, argv); - - free(la); - free(lb); - free(lo); - if (code && code < -256) { - die("Failed to execute 'merge'. merge(1) is used as the " - "file-level merge tool. Is 'merge' in your path?"); - } - fd = open(src1, O_RDONLY); - if (fd < 0 || fstat(fd, &st) < 0 || - index_fd(result.sha, fd, &st, 1, - "blob")) - die("Unable to add %s to database", src1); - - unlink(orig); - unlink(src1); - unlink(src2); - - result.clean = WEXITSTATUS(code) == 0; + mmfile_t orig, src1, src2; + mmbuffer_t result_buf; + xpparam_t xpp; + char *name1, *name2; + int merge_status; + + name1 = xstrdup(mkpath("%s/%s", branch1, a->path)); + name2 = xstrdup(mkpath("%s/%s", branch2, b->path)); + + fill_mm(o->sha1, &orig); + fill_mm(a->sha1, &src1); + fill_mm(b->sha1, &src2); + + memset(&xpp, 0, sizeof(xpp)); + merge_status = xdl_merge(&orig, + &src1, name1, + &src2, name2, + &xpp, XDL_MERGE_ZEALOUS, + &result_buf); + free(name1); + free(name2); + free(orig.ptr); + free(src1.ptr); + free(src2.ptr); + + if ((merge_status < 0) || !result_buf.ptr) + die("Failed to execute internal merge"); + + if (write_sha1_file(result_buf.ptr, result_buf.size, + blob_type, result.sha)) + die("Unable to add %s to database", + a->path); + + free(result_buf.ptr); + result.clean = (merge_status == 0); } else { if (!(S_ISLNK(a->mode) || S_ISLNK(b->mode))) die("cannot merge modes?"); @@ -889,7 +879,7 @@ static int process_renames(struct path_list *a_renames, struct diff_filespec src_other, dst_other; int try_merge, stage = a_renames == renames1 ? 3: 2; - remove_file(1, ren1_src, 1); + remove_file(1, ren1_src, index_only); hashcpy(src_other.sha1, ren1->src_entry->stages[stage].sha); src_other.mode = ren1->src_entry->stages[stage].mode; @@ -1061,38 +1051,17 @@ static int process_entry(const char *path, struct stage_data *entry, output("Adding %s", path); update_file(1, sha, mode, path); } - } else if (!o_sha && a_sha && b_sha) { - /* Case C: Added in both (check for same permissions). */ - if (sha_eq(a_sha, b_sha)) { - if (a_mode != b_mode) { - clean_merge = 0; - output("CONFLICT: File %s added identically in both branches, " - "but permissions conflict %06o->%06o", - path, a_mode, b_mode); - output("CONFLICT: adding with permission: %06o", a_mode); - update_file(0, a_sha, a_mode, path); - } else { - /* This case is handled by git-read-tree */ - assert(0 && "This case must be handled by git-read-tree"); - } - } else { - const char *new_path1, *new_path2; - clean_merge = 0; - new_path1 = unique_path(path, branch1); - new_path2 = unique_path(path, branch2); - output("CONFLICT (add/add): File %s added non-identically " - "in both branches. Adding as %s and %s instead.", - path, new_path1, new_path2); - remove_file(0, path, 0); - update_file(0, a_sha, a_mode, new_path1); - update_file(0, b_sha, b_mode, new_path2); - } - - } else if (o_sha && a_sha && b_sha) { + } else if (a_sha && b_sha) { + /* Case C: Added in both (check for same permissions) and */ /* case D: Modified in both, but differently. */ + const char *reason = "content"; struct merge_file_info mfi; struct diff_filespec o, a, b; + if (!o_sha) { + reason = "add/add"; + o_sha = (unsigned char *)null_sha1; + } output("Auto-merging %s", path); o.path = a.path = b.path = (char *)path; hashcpy(o.sha1, o_sha); @@ -1109,7 +1078,8 @@ static int process_entry(const char *path, struct stage_data *entry, update_file(1, mfi.sha, mfi.mode, path); else { clean_merge = 0; - output("CONFLICT (content): Merge conflict in %s", path); + output("CONFLICT (%s): Merge conflict in %s", + reason, path); if (index_only) update_file(0, mfi.sha, mfi.mode, path); @@ -534,7 +534,7 @@ int check_ref_format(const char *ref) level++; if (!ch) { if (level < 2) - return -1; /* at least of form "heads/blah" */ + return -2; /* at least of form "heads/blah" */ return 0; } } diff --git a/send-pack.c b/send-pack.c index 328dbbc16a..cc884f3b2d 100644 --- a/send-pack.c +++ b/send-pack.c @@ -406,6 +406,25 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) return ret; } +static void verify_remote_names(int nr_heads, char **heads) +{ + int i; + + for (i = 0; i < nr_heads; i++) { + const char *remote = strchr(heads[i], ':'); + + remote = remote ? (remote + 1) : heads[i]; + switch (check_ref_format(remote)) { + case 0: /* ok */ + case -2: /* ok but a single level -- that is fine for + * a match pattern. + */ + continue; + } + die("remote part of refspec is not a valid name in %s", + heads[i]); + } +} int main(int argc, char **argv) { @@ -457,6 +476,8 @@ int main(int argc, char **argv) usage(send_pack_usage); if (heads && send_all) usage(send_pack_usage); + verify_remote_names(nr_heads, heads); + pid = git_connect(fd, dest, exec); if (pid < 0) return 1; diff --git a/t/t0000-basic.sh b/t/t0000-basic.sh index 81f3bedc90..3260d1d7a7 100755 --- a/t/t0000-basic.sh +++ b/t/t0000-basic.sh @@ -19,11 +19,7 @@ modification *should* take notice and update the test vectors here. ' ################################################################ -# It appears that people are getting bitten by not installing -# 'merge' (usually part of RCS package in binary distributions). -# Check this and error out before running any tests. Also catch -# the bogosity of trying to run tests without building while we -# are at it. +# It appears that people try to run tests without building... ../git >/dev/null if test $? != 1 @@ -32,14 +28,6 @@ then exit 1 fi -merge >/dev/null 2>/dev/null -if test $? = 127 -then - echo >&2 'You do not seem to have "merge" installed. -Please check INSTALL document.' - exit 1 -fi - . ./test-lib.sh ################################################################ diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh new file mode 100644 index 0000000000..5d9b6f34b8 --- /dev/null +++ b/t/t6023-merge-file.sh @@ -0,0 +1,116 @@ +#!/bin/sh + +test_description='RCS merge replacement: merge-file' +. ./test-lib.sh + +cat > orig.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new1.txt << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +cat > new2.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new3.txt << EOF +DOMINUS regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +EOF + +cat > new4.txt << EOF +Dominus regit me, et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +EOF +echo -n "propter nomen suum." >> new4.txt + +cp new1.txt test.txt +test_expect_success "merge without conflict" \ + "git-merge-file test.txt orig.txt new2.txt" + +cp new1.txt test2.txt +test_expect_success "merge without conflict (missing LF at EOF)" \ + "git-merge-file test2.txt orig.txt new2.txt" + +test_expect_success "merge result added missing LF" \ + "diff -u test.txt test2.txt" + +cp test.txt backup.txt +test_expect_failure "merge with conflicts" \ + "git-merge-file test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< test.txt +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers" "diff -u test.txt expect.txt" + +cp backup.txt test.txt +test_expect_failure "merge with conflicts, using -L" \ + "git-merge-file -L 1 -L 2 test.txt orig.txt new3.txt" + +cat > expect.txt << EOF +<<<<<<< 1 +Dominus regit me, et nihil mihi deerit. +======= +DOMINUS regit me, +et nihil mihi deerit. +>>>>>>> new3.txt +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam tu mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +EOF + +test_expect_success "expected conflict markers, with -L" \ + "diff -u test.txt expect.txt" + +test_done + diff --git a/t/t6023-merge-rename-nocruft.sh b/t/t6023-merge-rename-nocruft.sh new file mode 100755 index 0000000000..69c66cf6fa --- /dev/null +++ b/t/t6023-merge-rename-nocruft.sh @@ -0,0 +1,97 @@ +#!/bin/sh + +test_description='Merge-recursive merging renames' +. ./test-lib.sh + +test_expect_success setup \ +' +cat >A <<\EOF && +a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +b bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb +c cccccccccccccccccccccccccccccccccccccccccccccccc +d dddddddddddddddddddddddddddddddddddddddddddddddd +e eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +f ffffffffffffffffffffffffffffffffffffffffffffffff +g gggggggggggggggggggggggggggggggggggggggggggggggg +h hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh +i iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii +j jjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjj +k kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk +l llllllllllllllllllllllllllllllllllllllllllllllll +m mmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm +n nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn +o oooooooooooooooooooooooooooooooooooooooooooooooo +EOF + +cat >M <<\EOF && +A AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA +B BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB +C CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC +D DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD +E EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +F FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF +G GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG +H HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +I IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII +J JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ +K KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK +L LLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL +M MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM +N NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN +O OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO +EOF + +git add A M && +git commit -m "initial has A and M" && +git branch white && +git branch red && + +git checkout white && +sed -e "/^g /s/.*/g : white changes a line/" <A >B && +sed -e "/^G /s/.*/G : colored branch changes a line/" <M >N && +rm -f A M && +git update-index --add --remove A B M N && +git commit -m "white renames A->B, M->N" && + +git checkout red && +echo created by red >R && +git update-index --add R && +git commit -m "red creates R" && + +git checkout master' + +# This test broke in 65ac6e9c3f47807cb603af07a6a9e1a43bc119ae +test_expect_success 'merge white into red (A->B,M->N)' \ +' + git checkout -b red-white red && + git merge white && + git write-tree >/dev/null || { + echo "BAD: merge did not complete" + return 1 + } + + test -f B || { + echo "BAD: B does not exist in working directory" + return 1 + } + test -f N || { + echo "BAD: N does not exist in working directory" + return 1 + } + test -f R || { + echo "BAD: R does not exist in working directory" + return 1 + } + + test -f A && { + echo "BAD: A still exists in working directory" + return 1 + } + test -f M && { + echo "BAD: M still exists in working directory" + return 1 + } + return 0 +' + +test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh new file mode 100644 index 0000000000..964010e764 --- /dev/null +++ b/t/t6024-recursive-merge.sh @@ -0,0 +1,80 @@ +#!/bin/sh + +test_description='Test merge without common ancestors' +. ./test-lib.sh + +# This scenario is based on a real-world repository of Shawn Pearce. + +# 1 - A - D - F +# \ X / +# B X +# X \ +# 2 - C - E - G + +export GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" +echo 1 > a1 +git add a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 + +git checkout -b A master +echo A > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 + +git checkout -b B master +echo B > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 + +git checkout -b D A +git-rev-parse B > .git/MERGE_HEAD +echo D > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D + +git symbolic-ref HEAD refs/heads/other +echo 2 > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 + +git checkout -b C +echo C > a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 + +git checkout -b E C +git-rev-parse B > .git/MERGE_HEAD +echo E > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E + +git checkout -b G E +git-rev-parse A > .git/MERGE_HEAD +echo G > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G + +git checkout -b F D +git-rev-parse C > .git/MERGE_HEAD +echo F > a1 +git update-index a1 +GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F + +test_expect_failure "combined merge conflicts" "git merge -m final G" + +cat > expect << EOF +<<<<<<< HEAD/a1 +F +======= +G +>>>>>>> 26f86b677eb03d4d956dbe108b29cb77061c1e73/a1 +EOF + +test_expect_success "result contains a conflict" "diff -u expect a1" + +git ls-files --stage > out +cat > expect << EOF +100644 f16f906ab60483c100d1241dfc39868de9ec9fcb 1 a1 +100644 cf84443e49e1b366fac938711ddf4be2d4d1d9e9 2 a1 +100644 fd7923529855d0b274795ae3349c5e0438333979 3 a1 +EOF + +test_expect_success "virtual trees were processed" "diff -u expect out" + +test_done diff --git a/wt-status.c b/wt-status.c index de1be5bc66..df582a03ef 100644 --- a/wt-status.c +++ b/wt-status.c @@ -297,11 +297,11 @@ void wt_status_print(struct wt_status *s) int git_status_config(const char *k, const char *v) { - if (!strcmp(k, "status.color")) { + if (!strcmp(k, "status.color") || !strcmp(k, "color.status")) { wt_status_use_color = git_config_colorbool(k, v); return 0; } - if (!strncmp(k, "status.color.", 13)) { + if (!strncmp(k, "status.color.", 13) || !strncmp(k, "color.status", 13)) { int slot = parse_status_slot(k, 13); color_parse(v, k, wt_status_colors[slot]); } diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index c9f817818a..fa409d5234 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -49,6 +49,9 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +#define XDL_MERGE_MINIMAL 0 +#define XDL_MERGE_EAGER 1 +#define XDL_MERGE_ZEALOUS 2 typedef struct s_mmfile { char *ptr; @@ -90,6 +93,10 @@ long xdl_mmfile_size(mmfile_t *mmf); int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdemitconf_t const *xecfg, xdemitcb_t *ecb); +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result); + #ifdef __cplusplus } #endif /* #ifdef __cplusplus */ diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index d76e76a0e6..9aeebc473b 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -45,7 +45,6 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl, xdalgoenv_t *xenv); static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2); -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); @@ -397,7 +396,7 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, } -static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) { long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec; char *rchg = xdf->rchg, *rchgo = xdfo->rchg; xrecord_t **recs = xdf->recs; diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index d3b72716b5..472aeaecfa 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -50,6 +50,7 @@ int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1, long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv); int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdfenv_t *xe); +int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags); int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr); void xdl_free_script(xdchange_t *xscr); int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb, diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c new file mode 100644 index 0000000000..352207e516 --- /dev/null +++ b/xdiff/xmerge.c @@ -0,0 +1,419 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2006 Davide Libenzi, Johannes E. Schindelin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Davide Libenzi <davidel@xmailserver.org> + * + */ + +#include "xinclude.h" + +typedef struct s_xdmerge { + struct s_xdmerge *next; + /* + * 0 = conflict, + * 1 = no conflict, take first, + * 2 = no conflict, take second. + */ + int mode; + long i1, i2; + long chg1, chg2; +} xdmerge_t; + +static int xdl_append_merge(xdmerge_t **merge, int mode, + long i1, long chg1, long i2, long chg2) +{ + xdmerge_t *m = *merge; + if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { + if (mode != m->mode) + m->mode = 0; + m->chg1 = i1 + chg1 - m->i1; + m->chg2 = i2 + chg2 - m->i2; + } else { + m = xdl_malloc(sizeof(xdmerge_t)); + if (!m) + return -1; + m->next = NULL; + m->mode = mode; + m->i1 = i1; + m->chg1 = chg1; + m->i2 = i2; + m->chg2 = chg2; + if (*merge) + (*merge)->next = m; + *merge = m; + } + return 0; +} + +static int xdl_cleanup_merge(xdmerge_t *c) +{ + int count = 0; + xdmerge_t *next_c; + + /* were there conflicts? */ + for (; c; c = next_c) { + if (c->mode == 0) + count++; + next_c = c->next; + free(c); + } + return count; +} + +static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, + int line_count, long flags) +{ + int i; + xrecord_t **rec1 = xe1->xdf2.recs + i1; + xrecord_t **rec2 = xe2->xdf2.recs + i2; + + for (i = 0; i < line_count; i++) { + int result = xdl_recmatch(rec1[i]->ptr, rec1[i]->size, + rec2[i]->ptr, rec2[i]->size, flags); + if (!result) + return -1; + } + return 0; +} + +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + xrecord_t **recs = xe->xdf2.recs + i; + int size = 0; + + if (count < 1) + return 0; + + for (i = 0; i < count; size += recs[i++]->size) + if (dest) + memcpy(dest + size, recs[i]->ptr, recs[i]->size); + if (add_nl) { + i = recs[count - 1]->size; + if (i == 0 || recs[count - 1]->ptr[i - 1] != '\n') { + if (dest) + dest[size] = '\n'; + size++; + } + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +{ + const int marker_size = 7; + int marker1_size = (name1 ? strlen(name1) + 1 : 0); + int marker2_size = (name2 ? strlen(name2) + 1 : 0); + int conflict_marker_size = 3 * (marker_size + 1) + + marker1_size + marker2_size; + int size, i1, j; + + for (size = i1 = 0; m; m = m->next) { + if (m->mode == 0) { + size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, + marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } else + size += conflict_marker_size; + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (dest) { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, + marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + } else if (m->mode == 1) + size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, + dest ? dest + size : NULL); + else if (m->mode == 2) + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, + m->i1 + m->chg2 - i1, 0, + dest ? dest + size : NULL); + i1 = m->i1 + m->chg1; + } + size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, + dest ? dest + size : NULL); + return size; +} + +/* + * Sometimes, changes are not quite identical, but differ in only a few + * lines. Try hard to show only these few lines as conflicting. + */ +static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, + xpparam_t const *xpp) +{ + for (; m; m = m->next) { + mmfile_t t1, t2; + xdfenv_t xe; + xdchange_t *xscr, *x; + int i1 = m->i1, i2 = m->i2; + + /* let's handle just the conflicts */ + if (m->mode) + continue; + + /* + * This probably does not work outside git, since + * we have a very simple mmfile structure. + */ + t1.ptr = (char *)xe1->xdf2.recs[m->i1]->ptr; + t1.size = xe1->xdf2.recs[m->i1 + m->chg1 - 1]->ptr + + xe1->xdf2.recs[m->i1 + m->chg1 - 1]->size - t1.ptr; + t2.ptr = (char *)xe2->xdf2.recs[m->i2]->ptr; + t2.size = xe2->xdf2.recs[m->i2 + m->chg2 - 1]->ptr + + xe2->xdf2.recs[m->i2 + m->chg2 - 1]->size - t2.ptr; + if (xdl_do_diff(&t1, &t2, xpp, &xe) < 0) + return -1; + if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe, &xscr) < 0) { + xdl_free_env(&xe); + return -1; + } + if (!xscr) { + /* If this happens, it's a bug. */ + xdl_free_env(&xe); + return -2; + } + x = xscr; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + while (xscr->next) { + xdmerge_t *m2 = xdl_malloc(sizeof(xdmerge_t)); + if (!m2) { + xdl_free_env(&xe); + xdl_free_script(x); + return -1; + } + xscr = xscr->next; + m2->next = m->next; + m->next = m2; + m = m2; + m->mode = 0; + m->i1 = xscr->i1 + i1; + m->chg1 = xscr->chg1; + m->i2 = xscr->i2 + i2; + m->chg2 = xscr->chg2; + } + xdl_free_env(&xe); + xdl_free_script(x); + } + return 0; +} + +/* + * level == 0: mark all overlapping changes as conflict + * level == 1: mark overlapping changes as conflict only if not identical + * level == 2: analyze non-identical changes for minimal conflict set + * + * returns < 0 on error, == 0 for no conflicts, else number of conflicts + */ +static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, + xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, + int level, xpparam_t const *xpp, mmbuffer_t *result) { + xdmerge_t *changes, *c; + int i1, i2, chg1, chg2; + + c = changes = NULL; + + while (xscr1 && xscr2) { + if (!changes) + changes = c; + if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i1 = xscr1->i2; + i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + continue; + } + if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + continue; + } + if (level < 1 || xscr1->i1 != xscr2->i1 || + xscr1->chg1 != xscr2->chg1 || + xscr1->chg2 != xscr2->chg2 || + xdl_merge_cmp_lines(xe1, xscr1->i2, + xe2, xscr2->i2, + xscr1->chg2, xpp->flags)) { + /* conflict */ + int off = xscr1->i1 - xscr2->i1; + int ffo = off + xscr1->chg1 - xscr2->chg1; + + i1 = xscr1->i2; + i2 = xscr2->i2; + if (off > 0) + i1 -= off; + else + i2 += off; + chg1 = xscr1->i2 + xscr1->chg2 - i1; + chg2 = xscr2->i2 + xscr2->chg2 - i2; + if (ffo > 0) + chg2 += ffo; + else + chg1 -= ffo; + if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + } + + i1 = xscr1->i1 + xscr1->chg1; + i2 = xscr2->i1 + xscr2->chg1; + + if (i1 >= i2) + xscr2 = xscr2->next; + if (i2 >= i1) + xscr1 = xscr1->next; + } + while (xscr1) { + if (!changes) + changes = c; + i1 = xscr1->i2; + i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg1 = xscr1->chg2; + chg2 = xscr1->chg1; + if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr1 = xscr1->next; + } + while (xscr2) { + if (!changes) + changes = c; + i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; + i2 = xscr2->i2; + chg1 = xscr2->chg1; + chg2 = xscr2->chg2; + if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + xdl_cleanup_merge(changes); + return -1; + } + xscr2 = xscr2->next; + } + if (!changes) + changes = c; + /* refine conflicts */ + if (level > 1 && xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0) { + xdl_cleanup_merge(changes); + return -1; + } + /* output */ + if (result) { + int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, + changes, NULL); + result->ptr = xdl_malloc(size); + if (!result->ptr) { + xdl_cleanup_merge(changes); + return -1; + } + result->size = size; + xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, + result->ptr); + } + return xdl_cleanup_merge(changes); +} + +int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, + mmfile_t *mf2, const char *name2, + xpparam_t const *xpp, int level, mmbuffer_t *result) { + xdchange_t *xscr1, *xscr2; + xdfenv_t xe1, xe2; + int status; + + result->ptr = NULL; + result->size = 0; + + if (xdl_do_diff(orig, mf1, xpp, &xe1) < 0 || + xdl_do_diff(orig, mf2, xpp, &xe2) < 0) { + return -1; + } + if (xdl_change_compact(&xe1.xdf1, &xe1.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe1.xdf2, &xe1.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe1, &xscr1) < 0) { + xdl_free_env(&xe1); + return -1; + } + if (xdl_change_compact(&xe2.xdf1, &xe2.xdf2, xpp->flags) < 0 || + xdl_change_compact(&xe2.xdf2, &xe2.xdf1, xpp->flags) < 0 || + xdl_build_script(&xe2, &xscr2) < 0) { + xdl_free_env(&xe2); + return -1; + } + status = 0; + if (xscr1 || xscr2) { + if (!xscr1) { + result->ptr = xdl_malloc(mf2->size); + memcpy(result->ptr, mf2->ptr, mf2->size); + result->size = mf2->size; + } else if (!xscr2) { + result->ptr = xdl_malloc(mf1->size); + memcpy(result->ptr, mf1->ptr, mf1->size); + result->size = mf1->size; + } else { + status = xdl_do_merge(&xe1, xscr1, name1, + &xe2, xscr2, name2, + level, xpp, result); + } + xdl_free_script(xscr1); + xdl_free_script(xscr2); + } + xdl_free_env(&xe1); + xdl_free_env(&xe2); + + return status; +} |