Michal Marek 9bee79
#!/usr/bin/env perl
Michal Marek 0a417c
#############################################################################
Michal Marek 0a417c
# Copyright (c) 2008,2009 Novell, Inc.
Michal Marek 0a417c
# All Rights Reserved.
Michal Marek 0a417c
#
Michal Marek 0a417c
# This program is free software; you can redistribute it and/or
Michal Marek 0a417c
# modify it under the terms of version 2 of the GNU General Public License as
Michal Marek 0a417c
# published by the Free Software Foundation.
Michal Marek 0a417c
#
Michal Marek 0a417c
# This program is distributed in the hope that it will be useful,
Michal Marek 0a417c
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Michal Marek 0a417c
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.   See the
Michal Marek 0a417c
# GNU General Public License for more details.
Michal Marek 0a417c
#
Michal Marek 0a417c
# You should have received a copy of the GNU General Public License
Michal Marek 0a417c
# along with this program; if not, contact Novell, Inc.
Michal Marek 0a417c
#
Michal Marek 0a417c
# To contact Novell about this file by physical or electronic mail,
Michal Marek 0a417c
# you may find current contact information at www.novell.com
Michal Marek 0a417c
#############################################################################
Michal Marek 0a417c
#
Michal Marek 9bee79
# script to merge changes done to *.changes files
Michal Marek 9bee79
#
Michal Marek 9bee79
# To install the script as a git merge driver:
Michal Marek 9bee79
#
Michal Marek 9bee79
#   git config merge.rpm-changes.name "*.changes merge driver"
Michal Marek 9bee79
#   git config merge.rpm-changes.driver "scripts/rpm-changes-merge.pl %A %O %B"
Michal Marek 9bee79
#   echo '*.changes merge=rpm-changes' >>.git/info/attributes
Michal Marek 9bee79
Michal Marek 9bee79
use strict;
Michal Marek 9bee79
use warnings;
Michal Marek 9bee79
Michal Marek 9bee79
BEGIN {
Michal Marek 9bee79
    if ($0 =~ /^(.*)\/[^\/]*/) {
Michal Marek 9bee79
        unshift @INC, "$1/lib";
Michal Marek 9bee79
    } else {
Michal Marek 9bee79
        unshift @INC,  "./lib";
Michal Marek 9bee79
    }
Michal Marek 9bee79
}
Michal Marek 9bee79
use Time::Zone;
Michal Marek 9bee79
use Getopt::Std;
Michal Marek 9bee79
use POSIX qw(mktime);
Michal Marek 9bee79
use File::Temp qw(tempfile);
Michal Marek 9bee79
Michal Marek 9bee79
my $conflicts = 0;
Michal Marek 9bee79
Michal Marek 9bee79
sub usage {
Michal Marek 9bee79
    print STDERR
Michal Marek 9bee79
"Usage:
Michal Marek 9bee79
  Three-way merge:
Michal Marek 9bee79
    $0 [-p] my.changes orig.changes their.changes
Michal Marek 9bee79
  Two-way merge:
Michal Marek 9bee79
    $0 [-p] -2 my.changes their.changes
Michal Marek 9bee79
  Fixup mode:
Michal Marek 9bee79
    $0 [-p] -1 my.changes
Michal Marek 9bee79
";
Michal Marek 9bee79
    exit 1;
Michal Marek 9bee79
}
Michal Marek 9bee79
Michal Marek 9bee79
sub main {
Michal Marek 9bee79
    $ENV{'TZ'} = "UTC";
Michal Marek 9bee79
Michal Marek 9bee79
    our ($opt_p, $opt_1, $opt_2, $opt_3) = (0, 0, 0, 0);
Michal Marek 9bee79
    my (%O, %A, %B, $out);
Michal Marek 9bee79
    if (!getopts('p123') || $opt_1 + $opt_2 + $opt_3 > 1) {
Michal Marek 9bee79
        usage();
Michal Marek 9bee79
    }
Michal Marek 9bee79
    if ($opt_1) {
Michal Marek 9bee79
        usage() if @ARGV != 1;
Michal Marek 9bee79
        loadchanges($ARGV[0], \%A);
Michal Marek 9bee79
    } elsif ($opt_2) {
Michal Marek 9bee79
        usage() if @ARGV != 2;
Michal Marek 9bee79
        loadchanges($ARGV[0], \%A);
Michal Marek 9bee79
        loadchanges($ARGV[1], \%B);
Michal Marek 9bee79
    } else {
Michal Marek 9bee79
        usage() if @ARGV != 3;
Michal Marek 9bee79
        loadchanges($ARGV[0], \%A);
Michal Marek 9bee79
        loadchanges($ARGV[1], \%O);
Michal Marek 9bee79
        loadchanges($ARGV[2], \%B);
Michal Marek 9bee79
    }
Michal Marek 9bee79
    if ($opt_p) {
Michal Marek 9bee79
        $out = \*STDOUT;
Michal Marek 9bee79
    } else {
Michal Marek 9bee79
        open($out, '>', $ARGV[0]) or die "$ARGV[0]: $!\n";
Michal Marek 9bee79
    }
Michal Marek 9bee79
Michal Marek 9bee79
    my %seen;
Michal Marek 9bee79
    for my $key (reverse(sort(keys(%A), keys(%O), keys(%B)))) {
Michal Marek 9bee79
        next if $seen{$key};
Michal Marek 9bee79
        $seen{$key} = 1;
Michal Marek 9bee79
        print $out merge($A{$key}, $O{$key}, $B{$key});
Michal Marek 9bee79
    }
Michal Marek 9bee79
    exit $conflicts;
Michal Marek 9bee79
}
Michal Marek 9bee79
Michal Marek 9bee79
sub merge {
Michal Marek 9bee79
    my ($a, $o, $b) = @_;
Michal Marek 9bee79
    $a = "" unless defined $a;
Michal Marek 9bee79
    $o = "" unless defined $o;
Michal Marek 9bee79
    $b = "" unless defined $b;
Michal Marek 9bee79
    return $a if $a eq $b;
Michal Marek 9bee79
    return $a if $o eq $b;
Michal Marek 9bee79
    return $b if $o eq $a;
Michal Marek 9bee79
    return rcs_merge($a, $o, $b);
Michal Marek 9bee79
}
Michal Marek 9bee79
Michal Marek 9bee79
my $have_rcs_merge = 1;
Michal Marek 9bee79
sub rcs_merge {
Michal Marek 9bee79
    my @texts = @_;
Michal Marek 9bee79
    my $res;
Michal Marek 9bee79
    if ($have_rcs_merge) {
Michal Marek 9bee79
        my @fh;
Michal Marek 9bee79
        my @fn;
Michal Marek 9bee79
        for my $i (0..2) {
Michal Marek 9bee79
            ($fh[$i], $fn[$i]) = tempfile();
Michal Marek 9bee79
            my $fh = $fh[$i];
Michal Marek 9bee79
            print $fh $texts[$i];
Michal Marek 9bee79
        }
Michal Marek 9bee79
        $res = `merge -p $fn[0] $fn[1] $fn[2]`;
Michal Marek 9bee79
        for my $i (0..2) {
Michal Marek 9bee79
            close($fh[$i]);
Michal Marek 9bee79
            unlink($fn[$i]);
Michal Marek 9bee79
        }
Michal Marek 9bee79
        if ($? == 0) {
Michal Marek 9bee79
            return $res;
Michal Marek 9bee79
        } elsif ($? >> 8 == 1) {
Michal Marek 9bee79
            $conflicts = 1;
Michal Marek 9bee79
            return $res;
Michal Marek 9bee79
        } else {
Michal Marek 9bee79
            print STDERR "merge(1) not found, using DumbMerge(TM) instead.\n";
Michal Marek 9bee79
            print STDERR "Install the rcs package for better merge results.\n";
Michal Marek 9bee79
            $have_rcs_merge = 0;
Michal Marek 9bee79
        }
Michal Marek 9bee79
    }
Michal Marek 9bee79
    return "<<<<<<< $ARGV[0]\n" . $texts[0] . "=======\n" . $texts[2] . ">>>>>>> $ARGV[2]\n";
Michal Marek 9bee79
}
Michal Marek 9bee79
Michal Marek 9bee79
sub loadchanges {
Michal Marek 9bee79
    my ($file, $res) = @_;
Michal Marek 9bee79
    open(my $fh, '<', $file) or die "$file: $!\n";
Michal Marek 9bee79
    my $l;
Michal Marek 9bee79
    my $date = 1<<32;
Michal Marek 9bee79
    my $email = "";
Michal Marek 9bee79
    my $expect_date = 0;
Michal Marek 9bee79
    my $text;
Michal Marek 9bee79
    while ($l = <$fh>) {
Michal Marek 9bee79
        if ($expect_date) {
Michal Marek 9bee79
            (my $dt, $email) = parse_date($l);
Michal Marek 9bee79
            if (defined $dt) {
Michal Marek 9bee79
                $date = $dt;
Michal Marek 9bee79
            } else {
Michal Marek 9bee79
                print STDERR "$file:$.: invalid date: $l";
Michal Marek 9bee79
            }
Michal Marek 9bee79
            $expect_date = 0;
Michal Marek 9bee79
        }
Michal Marek 9bee79
        if ($l =~ /^-{50}-*$/ || eof($fh)) {
Michal Marek 9bee79
            if (eof($fh)) {
Michal Marek 9bee79
                $text .= $l;
Michal Marek 9bee79
            }
Michal Marek 9bee79
            if (defined $text) {
Michal Marek 9bee79
                my $key = sprintf("%010d %s", $date, $email);
Michal Marek 9bee79
                if (defined $res->{$key}) {
Michal Marek 9bee79
                    $res->{$key} .= $text;
Michal Marek 9bee79
                } else {
Michal Marek 9bee79
                    $res->{$key} = $text
Michal Marek 9bee79
                }
Michal Marek 9bee79
            }
Michal Marek 9bee79
            undef $text;
Michal Marek 9bee79
            $date--;
Michal Marek 9bee79
            $expect_date = 1;
Michal Marek 9bee79
        }
Michal Marek 9bee79
        $text .= $l;
Michal Marek 9bee79
    }
Michal Marek 9bee79
    close($fh)
Michal Marek 9bee79
}
Michal Marek 9bee79
Michal Marek 9bee79
my %monthnum = (
Michal Marek 9bee79
    jan => 0,
Michal Marek 9bee79
    feb => 1,
Michal Marek 9bee79
    mar => 2,
Michal Marek 9bee79
    apr => 3,
Michal Marek 9bee79
    may => 4,
Michal Marek 9bee79
    jun => 5,
Michal Marek 9bee79
    jul => 6,
Michal Marek 9bee79
    aug => 7,
Michal Marek 9bee79
    sep => 8,
Michal Marek 9bee79
    oct => 9,
Michal Marek 9bee79
    nov => 10,
Michal Marek 9bee79
    dec => 11,
Michal Marek 9bee79
);
Michal Marek 9bee79
Michal Marek 9bee79
sub parse_date {
Michal Marek 9bee79
    my $l = shift;
Michal Marek 9bee79
    if ($l !~ /^(?:mon|tue|wed|thu|fri|sat|sun) +(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec) +(\d+) +(\d\d):(\d\d):(\d\d) +([A-Z]+) +(\d\d\d\d) +- +([^ ]*) *$/i) {
Michal Marek 9bee79
        return (undef, "");
Michal Marek 9bee79
    }
Michal Marek 9bee79
    my ($b, $d, $H, $M, $S, $Z, $Y, $email) = ($1, $2, $3, $4, $5, $6, $7, $8);
Michal Marek 9bee79
    my $date = mktime($S, $M, $H, $d, $monthnum{lc $b}, $Y - 1900);
Michal Marek 9bee79
    return (undef, "") unless defined $date;
Michal Marek 9bee79
    my $offset = tz_offset($Z);
Michal Marek 9bee79
    return (undef, "") unless defined $offset;
Michal Marek 9bee79
    chomp $email;
Michal Marek 9bee79
    return ($date - $offset, $email);
Michal Marek 9bee79
}
Michal Marek 9bee79
Michal Marek 9bee79
main();
Michal Marek 9bee79
Michal Marek 9bee79
# vim: sw=4:et