Dear diary, on Thu, Apr 14, 2005 at 01:14:13PM CEST, I got a letter where Junio C Hamano <[EMAIL PROTECTED]> told me that... > 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).
Bah, you outran me. ;-) > * 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. I think I will take it rather my working git merge implementation - it's getting insane in bash. ;-) I'll change it to use the cool git-pasky stuff (commit-id etc) and its style of committing - that is, it will merely record the update-caches to be done upon commit, and it will read-tree the branch we are merging to instead of the ancestor. (So that git diff gives useful output.) > * 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. -r traditionally means recursive - what's the reasoning behind the choice of this letter? > * 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; use strict? > + > +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]; > +} It'd be simpler to do just my @common = (map { $common{$_} } sort { $b <=> $a } keys %common) But I really think this is a horrible heuristic. I believe you should take the latest commit in the --edges output, and from that choose the base whose rev-tree --edges the_base merged_branch has the least lines on output. (That is, the path to it is shortest - ideally it's already part of the merged_branch.) > + > +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. What about sub OLDMODE { 0 } sub NEWMODE { 1 } sub NEWSHA { 2 } and then using that when accessing the tuple? Would make the code much more readable. > +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"; > +} It is a good idea to check merge's exit code and give a notice at the end if there were any conflicts. > + > +# 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'; Can't we call it just ,,merge? > +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"; Huh, what are you trying to do here? I think you should just record remove, no? (And I wouldn't do anything with my read-tree. ;-) > + } > +} > + > +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); > + } > + } > + } > +} ..snip.. Hmm, I think I will just need to play with the script a lot. ;-) -- Petr "Pasky" Baudis Stuff: http://pasky.or.cz/ C++: an octopus made by nailing extra legs onto a dog. -- Steve Taylor - 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