Here is a diff to update the git-merge.perl script I showed you earlier today ;-). It contains the following updates against your HEAD (bb95843a5a0f397270819462812735ee29796fb4).
* git-merge.perl command we talked about on the git list. I've covered the changed-to-the-same case etc. I still haven't done anything about file-vs-directory case yet. It does warn when it needed to run merge to automerge and let merge give a warning message about conflicts if any. In modify/remove cases, modified in one but removed in the other files are left in either $path~A~ or $path~B~ in the merge temporary directory, and the script issues a warning at the end. * show-files and ls-tree updates to add -z flag to NUL terminate records; this is needed for git-merge.perl to work. * show-diff updates to add -r flag to squelch diffs for files not in the working directory. This is mainly useful when verifying the result of an automated merge. * update-cache updates to add "--cacheinfo mode sha1" flag to register a file that is not in the current working directory. Needed for minimum-checkout merging by git-merge.perl. Signed-off-by: Junio C Hamano <[EMAIL PROTECTED]> --- git-merge.perl | 247 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ls-tree.c | 9 +- show-diff.c | 11 +- show-files.c | 12 ++ update-cache.c | 25 +++++ 5 files changed, 296 insertions(+), 8 deletions(-) diff -x .git -Nru ,,1/git-merge.perl ,,2/git-merge.perl --- ,,1/git-merge.perl 1969-12-31 16:00:00.000000000 -0800 +++ ,,2/git-merge.perl 2005-04-14 04:00:14.000000000 -0700 @@ -0,0 +1,247 @@ +#!/usr/bin/perl -w + +use Getopt::Long; + +my $full_checkout = 0; +my $oneside_checkout = 0; +GetOptions("full" => \$full_checkout, + "oneside" => \$oneside_checkout) + or die; + +if ($full_checkout) { + $oneside_checkout = 1; +} + +sub read_rev_tree { + my (@head) = @_; + my ($fhi); + open $fhi, '-|', 'rev-tree', '--edges', @head + or die "$!: rev-tree --edges @head"; + my %common; + while (<$fhi>) { + chomp; + (undef, undef, my @common) = split(/ /, $_); + for (@common) { + if (s/^([a-f0-f]{40}):3$/$1/) { + $common{$_}++; + } + } + } + close $fhi; + + my @common = (map { $_->[1] } + sort { $b->[0] <=> $a->[0] } + map { [ $common{$_} => $_ ] } + keys %common); + + return $common[0]; +} + +sub read_commit_tree { + my ($commit) = @_; + my ($fhi); + open $fhi, '-|', 'cat-file', 'commit', $commit + or die "$!: cat-file commit $commit"; + my $tree = <$fhi>; + close $fhi; + $tree =~ s/^tree //; + return $tree; +} + +# Reads diff-tree -r output and gives a hash that maps a path +# to 3-tuple (old-mode new-mode new-sha). +# When creating, old-mode is undef. When removing, new-* are undef. +sub read_diff_tree { + my (@tree) = @_; + my ($fhi); + local ($_, $/); + $/ = "\0"; + my %path; + open $fhi, '-|', 'diff-tree', '-r', @tree + or die "$!: diff-tree -r @tree"; + while (<$fhi>) { + chomp; + if (/^\*([0-7]+)->([0-7]+)\tblob\t[0-9a-f]+->([0-9a-f]{40})\t(.*)$/s) { + $path{$4} = [$1, $2, $3]; + } + elsif (/^\+([0-7]+)\tblob\t([0-9a-f]{40})\t(.*)$/s) { + $path{$3} = [undef, $1, $2]; + } + elsif (/^\-([0-7]+)\tblob\t[0-9a-f]{40}\t(.*)$/s) { + $path{$2} = [$1, undef, undef]; + } + else { + die "cannot parse diff-tree output: $_"; + } + } + close $fhi; + return %path; +} + +sub read_show_files { + my ($fhi); + local ($_, $/); + $/ = "\0"; + open $fhi, '-|', 'show-files', '-z' + or die "$!: show-files -z"; + my (@path) = map { chomp; $_ } <$fhi>; + close $fhi; + return @path; +} + +sub checkout_file { + my ($path, $info) = @_; + my (@elt) = split(/\//, $path); + my $j = ''; + my $tail = pop @elt; + my ($fhi, $fho); + for (@elt) { + mkdir "$j$_"; + $j = "$j$_/"; + } + open $fho, '>', "$path"; + open $fhi, '-|', 'cat-file', 'blob', $info->[2] + or die "$!: cat-file blob $info->[2]"; + while (<$fhi>) { + print $fho $_; + } + close $fhi; + close $fho; + chmod oct("0$info->[1]"), "$path"; +} + +sub record_file { + my ($path, $info) = @_; + system ('update-cache', '--add', '--cacheinfo', + $info->[1], $info->[2], $path); +} + +sub merge_tree { + my ($path, $info0, $info1) = @_; + checkout_file(',,merge-0', $info0); + checkout_file(',,merge-1', $info1); + system 'checkout-cache', $path; + my ($fhi, $fho); + open $fhi, '-|', 'merge', '-p', ',,merge-0', $path, ',,merge-1'; + open $fho, '>', "$path+"; + local ($/); + while (<$fhi>) { print $fho $_; } + close $fhi; + close $fho; + unlink ',,merge-0', ',,merge-1'; + rename "$path+", $path; + # There is no reason to prefer info0 over info1 but + # we need to pick one. + chmod oct("0$info0->[1]"), "$path"; +} + +# Find common ancestor of two trees. +my $common = read_rev_tree(@ARGV); +print "Common ancestor: $common\n"; + +# Create a temporary directory and go there. +system 'rm', '-rf', ',,merge-temp'; +for ((',,merge-temp', '.git')) { mkdir $_; chdir $_; } +symlink "../../.git/objects", "objects"; +chdir '..'; + +my $ancestor_tree = read_commit_tree($common); +system 'read-tree', $ancestor_tree; + +my %tree0 = read_diff_tree($ancestor_tree, read_commit_tree($ARGV[0])); +my %tree1 = read_diff_tree($ancestor_tree, read_commit_tree($ARGV[1])); + +my @ancestor_file = read_show_files(); +my %ancestor_file = map { $_ => 1 } @ancestor_file; + +for (@ancestor_file) { + if (! exists $tree0{$_} && ! exists $tree1{$_}) { + if ($full_checkout) { + system 'checkout-cache', $_; + } + print STDERR "O - $_\n"; + } +} + +for my $set ([\%tree0, \%tree1, 'A'], [\%tree1, \%tree0, 'B']) { + my ($treeA, $treeB, $side) = @$set; + while (my ($path, $info) = each %$treeA) { + # In this loop we do not deal with overlaps. + next if (exists $treeB->{$path}); + + if (! defined $info->[1]) { + # deleted in this tree only. + unlink $path; + system 'update-cache', '--remove', $path; + print STDERR "$side D $path\n"; + } + else { + # modified or created in this tree only. + print STDERR "$side M $path\n"; + if ($oneside_checkout) { + checkout_file($path, $info); + system 'update-cache', '--add', "$path"; + } else { + record_file($path, $info); + } + } + } +} + +my @warning = (); + +while (my ($path, $info0) = each %tree0) { + # We need to deal only with overlaps. + next if (!exists $tree1{$path}); + + my $info1 = $tree1{$path}; + if (! defined $info0->[1]) { + # deleted in this tree. + if (! defined $info1->[1]) { + # deleted in both trees. Obvious. + print STDERR "*DD $path\n"; + unlink $path; + system 'update-cache', '--remove', $path; + } + else { + # oops. tree0 wants to remove but tree1 wants to modify it. + print STDERR "*DM $path\n"; + checkout_file("$path~B~", $info1); + push @warning, $path; + } + } + else { + # modified or created in tree0 + if (! defined $info1->[1]) { + # oops. tree0 wants to modify but tree1 wants to remove it. + print STDERR "*MD $path\n"; + checkout_file("$path~A~", $info0); + push @warning, $path; + } + else { + # modified both in tree0 and tree1 + # are they modifying to the same contents? + if ($info0->[2] eq $info1->[2]) { + # just mode changes (or no changes) + # we prefer tree0 over tree1 for no particular reason. + print STDERR "*MM $path\n"; + record_file($path, $info0); + } + else { + # modified in both. Needs merge. + print STDERR "MRG $path\n"; + merge_tree($path, $info0, $info1); + } + } + } +} + +if (@warning) { + print "\nThere are some files that were deleted in one branch and\n" + . "modified in another. Please examine them carefully:\n"; + for (@warning) { + print "$_\n"; + } +} + +# system 'show-diff'; diff -x .git -Nru ,,1/ls-tree.c ,,2/ls-tree.c --- ,,1/ls-tree.c 2005-04-14 03:47:18.000000000 -0700 +++ ,,2/ls-tree.c 2005-04-14 04:00:14.000000000 -0700 @@ -5,6 +5,8 @@ */ #include "cache.h" +int line_termination = '\n'; + static int list(unsigned char *sha1) { void *buffer; @@ -31,7 +33,8 @@ * It seems not worth it to read each file just to get this * and the file size. -- [EMAIL PROTECTED] */ type = S_ISDIR(mode) ? "tree" : "blob"; - printf("%03o\t%s\t%s\t%s\n", mode, type, sha1_to_hex(sha1), path); + printf("%03o\t%s\t%s\t%s%c", mode, type, sha1_to_hex(sha1), + path, line_termination); } return 0; } @@ -40,6 +43,10 @@ { unsigned char sha1[20]; + if (argc == 3 && !strcmp(argv[1], "-z")) { + line_termination = 0; + argc--; argv++; + } if (argc != 2) usage("ls-tree <key>"); if (get_sha1_hex(argv[1], sha1) < 0) diff -x .git -Nru ,,1/show-diff.c ,,2/show-diff.c --- ,,1/show-diff.c 2005-04-14 03:47:18.000000000 -0700 +++ ,,2/show-diff.c 2005-04-14 04:00:14.000000000 -0700 @@ -58,15 +58,20 @@ int main(int argc, char **argv) { int silent = 0; + int silent_on_nonexisting_files = 0; int entries = read_cache(); int i; while (argc-- > 1) { if (!strcmp(argv[1], "-s")) { - silent = 1; + silent_on_nonexisting_files = silent = 1; continue; } - usage("show-diff [-s]"); + if (!strcmp(argv[1], "-r")) { + silent_on_nonexisting_files = 1; + continue; + } + usage("show-diff [-s] [-r]"); } if (entries < 0) { @@ -83,7 +88,7 @@ if (stat(ce->name, &st) < 0) { printf("%s: %s\n", ce->name, strerror(errno)); - if (errno == ENOENT && !silent) + if (errno == ENOENT && !silent_on_nonexisting_files) show_diff_empty(ce); continue; } diff -x .git -Nru ,,1/show-files.c ,,2/show-files.c --- ,,1/show-files.c 2005-04-14 03:47:18.000000000 -0700 +++ ,,2/show-files.c 2005-04-14 04:00:14.000000000 -0700 @@ -14,6 +14,7 @@ static int show_cached = 0; static int show_others = 0; static int show_ignored = 0; +static int line_terminator = '\n'; static const char **dir; static int nr_dir; @@ -105,12 +106,12 @@ } if (show_others) { for (i = 0; i < nr_dir; i++) - printf("%s\n", dir[i]); + printf("%s%c", dir[i], line_terminator); } if (show_cached) { for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; - printf("%s\n", ce->name); + printf("%s%c", ce->name, line_terminator); } } if (show_deleted) { @@ -119,7 +120,7 @@ struct stat st; if (!stat(ce->name, &st)) continue; - printf("%s\n", ce->name); + printf("%s%c", ce->name, line_terminator); } } if (show_ignored) { @@ -134,6 +135,11 @@ for (i = 1; i < argc; i++) { char *arg = argv[i]; + if (!strcmp(arg, "-z")) { + line_terminator = 0; + continue; + } + if (!strcmp(arg, "--cached")) { show_cached = 1; continue; diff -x .git -Nru ,,1/update-cache.c ,,2/update-cache.c --- ,,1/update-cache.c 2005-04-14 03:47:18.000000000 -0700 +++ ,,2/update-cache.c 2005-04-14 04:00:14.000000000 -0700 @@ -250,6 +250,8 @@ { int i, newfd, entries; int allow_options = 1; + const char *sha1_force = NULL; + const char *mode_force = NULL; newfd = open(".git/index.lock", O_RDWR | O_CREAT | O_EXCL, 0600); if (newfd < 0) @@ -282,14 +284,35 @@ refresh_cache(); continue; } + if (!strcmp(path, "--cacheinfo")) { + mode_force = argv[++i]; + sha1_force = argv[++i]; + continue; + } die("unknown option %s", path); } if (!verify_path(path)) { fprintf(stderr, "Ignoring path %s\n", argv[i]); continue; } - if (add_file_to_cache(path)) + if (sha1_force && mode_force) { + struct cache_entry *ce; + int namelen = strlen(path); + int mode; + int size = cache_entry_size(namelen); + sscanf(mode_force, "%o", &mode); + ce = malloc(size); + memset(ce, 0, size); + memcpy(ce->name, path, namelen); + ce->namelen = namelen; + ce->st_mode = mode; + get_sha1_hex(sha1_force, ce->sha1); + + add_cache_entry(ce, 1); + } + else if (add_file_to_cache(path)) die("Unable to add %s to database", path); + mode_force = sha1_force = NULL; } if (write_cache(newfd, active_cache, active_nr) || rename(".git/index.lock", ".git/index")) - To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to [EMAIL PROTECTED] More majordomo info at http://vger.kernel.org/majordomo-info.html