]> granicus.if.org Git - nethack/commitdiff
initial git setup MOVE2GIT
authorkeni <keni@his.com>
Thu, 12 Feb 2015 00:42:57 +0000 (19:42 -0500)
committerkeni <keni@his.com>
Thu, 12 Feb 2015 00:42:57 +0000 (19:42 -0500)
24 files changed:
.gitattributes [new file with mode: 0644]
DEVEL/.gitattributes.swp [new file with mode: 0644]
DEVEL/DOTGIT/TARGET [new file with mode: 0644]
DEVEL/Developer.txt [new file with mode: 0644]
DEVEL/hooksdir/.NHgithook.pm.swp [new file with mode: 0644]
DEVEL/hooksdir/NHadd [new file with mode: 0755]
DEVEL/hooksdir/NHgithook.pm [new file with mode: 0644]
DEVEL/hooksdir/NHsubst [new file with mode: 0755]
DEVEL/hooksdir/NHtext [new file with mode: 0755]
DEVEL/hooksdir/TARGET [new file with mode: 0644]
DEVEL/hooksdir/applypatch-msg [new file with mode: 0755]
DEVEL/hooksdir/commit-msg [new file with mode: 0755]
DEVEL/hooksdir/post-applypatch [new file with mode: 0755]
DEVEL/hooksdir/post-checkout [new file with mode: 0755]
DEVEL/hooksdir/post-commit [new file with mode: 0755]
DEVEL/hooksdir/post-merge [new file with mode: 0755]
DEVEL/hooksdir/post-rewrite [new file with mode: 0755]
DEVEL/hooksdir/pre-applypatch [new file with mode: 0755]
DEVEL/hooksdir/pre-auto-gc [new file with mode: 0755]
DEVEL/hooksdir/pre-commit [new file with mode: 0755]
DEVEL/hooksdir/pre-push [new file with mode: 0755]
DEVEL/hooksdir/pre-rebase [new file with mode: 0755]
DEVEL/hooksdir/prepare-commit-msg [new file with mode: 0755]
DEVEL/nhgitset.pl [new file with mode: 0755]

diff --git a/.gitattributes b/.gitattributes
new file mode 100644 (file)
index 0000000..72533db
--- /dev/null
@@ -0,0 +1,5 @@
+*.[ch] filter=NHtext merge=NHsubst
+* text=auto
+*.hqx -text
+*.sln -text
+*.vcxproj -text
diff --git a/DEVEL/.gitattributes.swp b/DEVEL/.gitattributes.swp
new file mode 100644 (file)
index 0000000..1d91c53
Binary files /dev/null and b/DEVEL/.gitattributes.swp differ
diff --git a/DEVEL/DOTGIT/TARGET b/DEVEL/DOTGIT/TARGET
new file mode 100644 (file)
index 0000000..6b8710a
--- /dev/null
@@ -0,0 +1 @@
+.git
diff --git a/DEVEL/Developer.txt b/DEVEL/Developer.txt
new file mode 100644 (file)
index 0000000..7c4fa49
--- /dev/null
@@ -0,0 +1,164 @@
+ ___              _                   
+ |   \ _____ _____| |___ _ __  ___ _ _ 
+ | |) / -_) V / -_) / _ \ '_ \/ -_) '_|
+ |___/\___|\_/\___|_\___/ .__/\___|_|  
+                        |_|            
+
+$NHDT-Date$
+
+Welcome to the NetHack Infrastructure Developer's Guide.
+
+This is the info you need if you are developing code for NetHack.
+(This information is from DevTeam.  If you are working with a variant please
+check for additional documentation for that variant.)
+
+For information on building NetHack, see README in the top level directory.
+For information on playing NetHack, see the Guidebook in the doc directory.
+
+DANGER!  WORK IN PROGRESS!  Known issues marked XXX.
+
+CONTENTS
+1. email
+2. git repositories
+3. bug reporting
+4. git configuration
+5. variable expansion
+6. reserved names
+------------------------------------------------------------------------------
+1. email
+Email to devteam@nethack.org will usually get a response, but it may take a
+while.  Please do not send save files, binary screen grabs, or other large
+things.
+------------------------------------------------------------------------------
+2. git repositories
+The public NetHack git repository is available (read-only) on SourceForge at:
+  git://git.code.sf.net/p/nethack/NHsource
+------------------------------------------------------------------------------
+3. bug reporting
+Please use the form at http://www.nethack.org/common/contact.html (or send
+us an email if that's more appropriate).
+------------------------------------------------------------------------------
+4. git configuration
+A. If you have never set up git on this machine before:
+   (This assumes you will only be using git for NetHack.  If you are going to
+   use it for other projects as well, think before you type.)
+       Tell git what name (or nicname) and email address to use for you:
+               git config --global user.name "MY NAME"
+               git config --global user.email USER@EXAMPLE.COM
+   You probably want to set up a credential cache.
+       Mac OS X:
+               git config --global credential.helper osxkeychain
+XXX windows
+XXX linux
+B. Specify the prefix for variable substitution:
+   (This assumes you are not a member of DevTeam or any variant's development
+   team.  If you are, this may be wrong.  Look for more specific documentation.)
+       Decide where you want to put this info; it should NOT be inside the
+       tree you cloned from git.  I use ~/nethack/GITADDDIR; for that base,
+       create the needed directories and edit the file:
+               ~/nethack/GITADDDIR/DOTGIT/PRE
+       Put this in it (if your OS is not Unix-like you may need to change
+       the first line):
+               #!/bin/sh
+               git config nethack.substprefix MINE
+C. Configure the repository:
+       - cd to the top level of the repository
+       - tell the repository about the directory you created above:
+               git config nethack.gitadddir FULL_PATH_TO_GITADDDIR
+          so for the example above:
+               git config nethack.gitadddir ~/nethack/GITADDDIR
+       - do the automated setup:
+               perl DEVEL/nhgitset.pl
+         If it complains, fix what it complains about.  nhgitset.pl accepts
+         the following options:
+               -v verbose
+               -n dry run
+   You can re-run nhgitset.pl as often as needed; occasionally we will
+   update it and ask you to run it again.
+D. aliases
+   Two aliases are installed by nhgitset.pl:
+       nhadd
+       nhcommit
+   These two commands take the same options as the normal git add and commit
+   commands but perform RCS/CVS-style variable substitution.  Note that the
+   substitutions do not show up in the working directory.
+
+   Note that nothing terrible will happen if you do not use the nh* versions
+   of the commands.
+
+   Supported substitutions:
+       MINE-Date       the commit time and date
+   Experimental substitutions:
+       MINE-Revision   CVS style revision number
+       MINE-Branch     the current git branch
+
+That's it.  If you need to do something more when setting up your repository,
+keep reading.  Otherwise, you are done with this section.
+
+1) to run your own hooks in addition to ours:
+       name your hook
+               WHEN-HOOKNAME
+       where WHEN is
+               PRE     (run your code before the NetHack hook)
+               POST    (run your code after the NetHack hook)
+       and HOOKNAME is the normal git name of the hook.
+   Be sure to test carefully since the composition of two bits of code may or
+   may not do what you want.
+2) to install other bits on setup:
+       Put additional files in the GITADDDIR tree.  Use "DOTGIT" instead of
+       ".git".  If a file called PRE, POST, or INSTEAD exists in a
+       subdirectory of GITADDDIR, it is run before the copy, after the copy,
+       or instead of the copy.  No copy operation is attempted in the DOTGIT
+       directory; use a script and standard git commands to change the
+       contents as needed.
+3) NB: In all namespaces, anything that matches m/^nh/i or m/^nethack/i is
+   reserved.
+------------------------------------------------------------------------------
+5. variable expansion
+A. Introduction
+   We have implemented an RCS/CVS/SVN style variable expansion mechanism.
+   References of either of the formats:
+       $PREFIX-VARNAME$
+       $PREFIX-VARNAME: VALUE $
+   will be handled (if enabled).
+
+   The PREFIX is the value in the git config variable nethack.substprefix.
+   VARNAME is one of:
+       Date
+       Branch (experimental)
+       Revision (experimental)
+   other names will give a warning.
+
+B. Enabling variable expansion
+   Variable expansion is controlled by the .gitattributes file.
+
+   To enable variable expansion:
+        pattern filter=NHtext merge=NHsubst
+   To disable variable expansion:
+       pattern -filter
+
+   More information: "git help gitattributes"
+
+C. Oddities
+   To trigger variable expansion, you _must_ use "git nhadd" or "git nhcommit"
+   instead of "git add" or "git commit."  Nothing terrible will happen if you
+   use the wrong one, but the values will not be updated.
+
+   Due to the way this abuses git filters, the updated values are not visible
+   in your working tree.
+
+D. Using your own hooks
+   You can use your own hooks - put them in .git/hooks as usual BUT name them
+   as follows:
+       WHEN-HOOKNAME
+   where WHEN is:
+       PRE     (execute the code before the NetHack hook)
+       POST    (execute the code after the NetHack hook)
+   and HOOKNAME is the normal git name for the hook.
+
+   Test carefully - interactions between hooks can be nasty.
+------------------------------------------------------------------------------
+6. reserved names
+   Anything that matches m/^nh/i or m/^nethack/i is reserved in all
+   namespaces (environment, file names, git config, etc).
+------------------------------------------------------------------------------
diff --git a/DEVEL/hooksdir/.NHgithook.pm.swp b/DEVEL/hooksdir/.NHgithook.pm.swp
new file mode 100644 (file)
index 0000000..19e7ec8
Binary files /dev/null and b/DEVEL/hooksdir/.NHgithook.pm.swp differ
diff --git a/DEVEL/hooksdir/NHadd b/DEVEL/hooksdir/NHadd
new file mode 100755 (executable)
index 0000000..55a86ee
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+# wrapper for nhadd and nhcommit aliases
+# $NHDT-Date$
+
+%ok = map { $_ => 1 } ('add', 'commit');
+
+die "Bad subcommand '$ARGV[0]'" unless $ok{$ARGV[0]};
+
+if(length $ENV{GIT_PREFIX}){
+        chdir($ENV{GIT_PREFIX}) or die "Can't chdir $ENV{GIT_PREFIX}: $!";
+}
+
+$ENV{NHMODE} = 1;
+exec "git", @ARGV or die "Can't exec git: $!";
diff --git a/DEVEL/hooksdir/NHgithook.pm b/DEVEL/hooksdir/NHgithook.pm
new file mode 100644 (file)
index 0000000..6024c1c
--- /dev/null
@@ -0,0 +1,205 @@
+#
+# NHgithook.pm
+# NetHack Git Hook Module
+# $NHDT-Date$
+
+package NHgithook;
+use Cwd;
+
+###
+### CONFIG
+###
+my $trace = 0;
+my $tracefile = "/tmp/nhgitt.$$";
+
+# OS hackery
+my $DS = quotemeta('/');
+if ($^O eq "MSWin32")
+{
+    $DS = quotemeta('\\');
+}
+
+our %saved_env;
+our @saved_argv;
+our $saved_input;
+
+sub saveSTDIN {
+       @saved_input = <STDIN>;
+
+       if($trace){
+               print TRACE "STDIN:\n";
+               print TRACE $saved_input;
+               print TRACE "ENDSTDIN\n";
+       }
+
+       tie *STDIN, 'NHIO::STDIN', @saved_input;
+}
+
+# XXX this needs a re-write (don't tie and untie, just set NEXT=0)
+#  (the sensitive thing is @foo = <STDIN> )
+sub resetSTDIN{
+        my $x = tied(*STDIN);
+        my %x = %$x;
+        my $data = @$x{DATA};
+        untie *STDIN;
+        tie *STDIN, 'NHIO::STDIN', $data;
+}
+
+# don't need this now
+#sub restore {
+#      open STDIN, "<", \$saved_input or die "reopen STDIN: $!";
+#      @ARGV = @saved_argv;
+#      %ENV = %saved_env;
+#}
+
+sub PRE {
+       &do_hook("PRE");
+}
+
+sub POST {
+       &do_hook("POST");
+}
+
+# PRIVATE
+sub do_hook {
+       my($p) = @_;
+       my $hname = $0;
+       $hname =~ s!^((.*$DS)|())(.*)!$1$p-$4!;
+       if(-x $hname){
+               print TRACE "START $p: $hname\n" if($trace);
+
+               open TOHOOK, "|-", $hname or die "open $hname: $!";
+               print TOHOOK <STDIN>;
+               close TOHOOK or die "close $hname: $! $?";
+
+               print TRACE "END $p\n" if($trace);
+       }
+}
+
+sub trace_start {
+       return unless($trace);
+       my $self = shift;
+       open TRACE, ">>", $tracefile;
+       print TRACE "START CLIENT PID:$$ ARGV:\n";
+       print TRACE "CWD: " . cwd() . "\n";
+       print TRACE "[0] $0\n";
+       my $x1;
+       for(my $x=0;$x<scalar @ARGV;$x++){
+               $x1 = $x+1;
+               print TRACE "[$x1] $ARGV[$x]\n";
+       }
+       print TRACE "ENV:\n";
+       foreach my $k (sort keys %ENV){
+               next unless ($k =~ m/(^GIT_)|(^NH)/);
+               print TRACE " $k => $ENV{$k}\n";
+       }
+}
+
+BEGIN {
+       %saved_env = %ENV;
+       @saved_argv = @ARGV;
+       &trace_start;
+}
+
+###
+### ugly mess so we can re-read STDIN
+###
+package NHIO::STDIN;
+sub TIEHANDLE {
+        my $class = shift;
+        my %fh;
+                # XXX yuck
+        if(ref @_[0]){
+                $fh{DATA} = @_[0];
+        } else {
+                $fh{DATA} = \@_;
+        }   
+        $fh{NEXT} = 0;
+        return bless \%fh, $class;
+}
+
+sub READLINE {
+        my $self = shift;
+        return undef if($self->{EOF});
+        if(wantarray){
+                my $lim = $#{$self->{DATA}};
+                my @ary = @{$self->{DATA}}[$self->{NEXT}..$lim];
+                my @rv = @ary[$self->{NEXT}..$#ary];
+                $self->{EOF} = 1;
+                return @rv;
+        } else{
+                my $rv = $self->{DATA}[$self->{NEXT}];
+                if(length $rv){
+                        $self->{NEXT}++;
+                        return $rv;
+                } else {
+                        $self->{EOF} = 1;
+                        return undef;
+                }   
+        }   
+}
+
+sub EOF {
+        $self = shift;
+        return $self->{EOF};
+}
+
+1;
+__END__
+
+=head1 NAME
+
+NHgithook - common code for NetHack git hooks (and other git bits)
+
+=head1 SYNOPSIS
+
+  BEGIN {
+        my $DS = quotemeta('/');
+       my $PDS = '/';
+        if ($^O eq "MSWin32")
+        {
+            $DS = quotemeta('\\');
+           $PDS = '\\';
+        }
+
+        push(@INC, $ENV{GIT_DIR}.$PDS."hooks");    # for most hooks
+        push(@INC, ($0 =~ m!^(.*)$DS!)[0]);      # when the above doesn't work
+
+        $gitdir = `git rev-parse --git-dir`;      # and when the above really doesn't work
+        $gitdir =~ s/[\r\n]*$/;
+        push(@INC, $gitdir.$PDS."hooks");
+  }
+  use NHgithook;
+  
+  &NHgithook::saveSTDIN;
+  &NHgithook::PRE;
+  (core hook code)
+  &NHgithook::POST;
+
+=head1 DESCRIPTION
+
+Buffers call information so multiple independent actions may be coded for
+Git hooks and similar Git callouts.
+
+=head1 SETUP
+
+Changing the C<$trace> and C<$tracefile> variables requires editing the
+module source.  Setting C<$trace> enables tracing, logs basic information,
+and leaves the C<TRACE> filehandle open for additional output; output to this
+filehandle must be guarded by C<$NHgithook::trace>.  Setting
+C<$tracefile> specifies the file used for trace output.  Note that C<$$>
+may be useful since multiple processes may be live at the same time.
+
+=head1 FUNCTIONS
+
+  NHgithook::saveSTDIN  reads STDIN until EOF and saves it
+  NHgithook::PRE       runs the PRE hook, if it exists
+  NHgithook::POST      runs the POST hook, if it exists
+
+=head1 BUGS
+
+Some features not well tested, especially under Windows.
+
+=head1 AUTHOR
+
+Kenneth Lorber (keni@his.com)
diff --git a/DEVEL/hooksdir/NHsubst b/DEVEL/hooksdir/NHsubst
new file mode 100755 (executable)
index 0000000..2c0c3c7
--- /dev/null
@@ -0,0 +1,366 @@
+#!/usr/bin/perl
+#
+# NHsubst
+# $NHDT-Date$
+# git merge driver for substitutions (like RCS/CVS)
+# driver line:   .... %O %A %B %L
+use strict;
+
+my $debug = 0;
+my $rawin = 0; # feed diff to stdin for testing (do NOT set $debug=1)
+
+# We want TRACE open so we don't need to test $debug everywhere, but we skip
+# this first block because it's expensive and dumpfile() hangs with $rawin.
+my $sink = ($^O eq "MSWin32") ? "NUL" : "/dev/null";
+my $dbgfile = ($^O eq "MSWin32") ? "$ENV{TEMP}.$$" : "/tmp/trace.$$";
+open TRACE, ">>", ($debug==0)? $sink : $dbgfile;
+if($debug){
+       print TRACE "START CLIENT ARGV:\n";
+       print TRACE "[0] $0\n";
+       my $x1;
+       for(my $x=0;$x<scalar @ARGV;$x++){
+               $x1 = $x+1;
+               print TRACE "[$x1] $ARGV[$x]\n";
+       }
+       print TRACE "ENV:\n";
+       foreach my $k (sort keys %ENV){
+               next unless ($k =~ m/^GIT_/);
+               print TRACE " $k => $ENV{$k}\n";
+       }
+       print TRACE "CWD: " . `pwd`;
+       &dumpfile($ARGV[0], "[0O]");
+       &dumpfile($ARGV[1], "[1A]");
+       &dumpfile($ARGV[2], "[2B]");
+       print TRACE "L=$ARGV[3]\n";
+       print TRACE "END\n";
+}
+
+my $mark_len = $ARGV[3];
+$mark_len = 3 if($mark_len==0 && $rawin);
+
+my $mark_start = '<' x $mark_len;
+my $mark_middle = '=' x $mark_len;
+my $mark_end = '>' x $mark_len;
+
+my $PREFIX;
+# pick up the prefix for substitutions in this repo
+if($rawin){
+       $PREFIX = "TEST";
+} else {
+       $PREFIX = `git config --local --get nethack.substprefix`;
+       chomp($PREFIX);
+}
+
+my @out;
+my $cntout;
+if($rawin){
+       @out = <STDIN>;
+} else {
+       #system "git merge-file -p .... > temp
+       my $tags = "-L CURRENT -L ANCESTOR -L OTHER";   # XXX should "CURRENT" be "MINE"?
+       @out = `git merge-file -p $tags $ARGV[1] $ARGV[0] $ARGV[2]`;
+       #NB: we don't check the exit value because it's useless
+       print TRACE "MERGE-FILE START\n".join("",@out)."MERGE-FILE END\n";
+}
+
+($cntout,@out) = &edit_merge(@out);
+
+if($rawin){
+       print "COUNT: $cntout\n";
+       print @out;
+} else {
+       # spit @out to $ARGV[1]  (careful: what about EOL character?)
+       open OUT, ">$ARGV[1]" or die "Can't open $ARGV[1]";
+       print OUT @out;
+       close OUT;
+
+       print TRACE "WRITING START ($ARGV[1])\n".join("",@out)."WRITING END\n";
+       &dumpfile($ARGV[1], "READBACK");
+}
+print TRACE "COUNT: $cntout\n";
+
+exit( ($cntout>0) ? 1 : 0);
+
+#git merge-file [-L <current-name> [-L <base-name> [-L <other-name>]]]
+#               [--ours|--theirs|--union] [-p|--stdout] [-q|--quiet] [--marker-size=<n>]
+#               [--[no-]diff3] <current-file> <base-file> <other-file>
+#The `merge.*.driver` variable's value is used to construct a command to run to merge ancestor's
+#           version (%O), current version (%A) and the other branches' version (%B). These three tokens are
+#           replaced with the names of temporary files that hold the contents of these versions when the
+#           command line is built. Additionally, %L will be replaced with the conflict marker size (see
+#           below).
+
+# keep failing so we don't need to keep changing the setup while building this script
+
+sub dumpfile {
+       my($file, $tag) = @_;
+       print TRACE "FILE $tag START\n";
+       print TRACE `hexdump -C $file`;
+       print TRACE "FILE END\n";
+}
+
+sub edit_merge {
+       my(@input) = @_;
+                                       # $::count is a bit ugly XXX
+       local $::count = 0;             # we need the number of conflicts for exit()
+       my @out;
+
+       local $_;
+       while($_ = shift @input){
+               if(m/^$mark_start /){
+                       print TRACE "FOUND A CONFLICT\n";
+                       my @conflict;
+                       push(@conflict, $_);
+                       while($_ = shift @input){
+                               push(@conflict, $_);
+                               if(m/^$mark_end /){
+                                       last;
+                               }
+                       }
+                       push(@out, &edit_conflict(@conflict));
+               } else {
+                       push(@out, $_);
+               }
+       }
+       print TRACE "RETURN count=$::count\n";
+       return($::count, @out);
+}
+
+sub edit_conflict {
+       my(@in) = @_;
+
+       print TRACE "EDIT START: " . scalar(@in)."\n";
+       if($debug){
+               foreach my $x (@in){ my $xx = $x; chomp($xx); print TRACE "-$xx-\n"; }
+       }
+       print TRACE "EDIT END INPUT\n";
+
+               # one-line change - use as base case to develop the code
+               #   ours        ARGV[1] top-of-diff
+               #   theirs      ARGV[2] bottom-of-diff
+               # simple conflict:
+               # [0] <<<<<<< d1
+               # [1] $$PREFIX-Date: 1 ...
+               # [2] =======
+               # [3] $$PREFIX-Date: 3 ...
+               # [4] >>>>>>> d3
+       if(scalar(@in) == 5 && $in[2] =~ m/^$mark_middle/){
+               my $back = &merge_one_line_maybe($in[1],$in[3]);        # (ours, theirs)
+               if(!defined $back){
+                       $::count++;     # leave the conflict
+                       return @in;
+               } else {
+                       return ($back);
+               }
+               # NOTREACHED
+       } else {
+# XXX LATER
+# Start at the top of both sections and work downwards.  As long as the lines can be merged,
+# push them out and keep going.  If there are lines left, we will still have a conflict but
+# we can try to make it smaller.  Push out the start-conflict marker.  Start at the
+# bottom of both section and work upwards.  As long as the lines can be merged, reverse push out
+# the merged line and keep going.  (We know there will be lines left at some point.)  Push out
+# remaining (middle) lines from OURS.  Push out mark_middle.  Push out remaining middle lines
+# from THEIRS.  Push out end-conflict marker.  $::count++; return (@a,$b,@c,$d,@e,$f,@g)
+# @a
+# $b = <<<
+# @c
+# $d = ===
+# @e
+# $f = >>>
+# @g
+       }
+               # not matched - return the unchanged conflict
+       $::count++;
+       return @in;
+}
+
+# XXX This is expensive.  Add a quick check for "anything that looks like a subst var" and just
+#  declare the lines unmergeable if it fails.
+sub merge_one_line_maybe {
+       my($ours, $theirs) = @_;
+
+       my $more = 1;
+       my $fail = 0;
+       my $out = '';
+               # TYPES:
+               # 0 no match
+               # 1 unexpanded var
+               # 2 expanded var
+               # 3 non-var text
+       my($ourstype, $theirtype);
+       my($oursvar, $theirvar);
+       my($oursval, $theirval);
+
+       while($more){
+               ($ourstype, $theirtype) = (0,0);
+               ($oursvar, $theirvar) = (undef, undef);
+               ($oursvar, $theirvar) = (undef, undef);
+                       # unexpanded var
+               if($ours =~ m/\G\$$PREFIX-([A-Z][a-z]+)\$/gc){
+                       $ourstype = 1;
+                       $oursvar = $1;
+               }
+               if($theirs =~ m/\G\$$PREFIX-([A-Z][a-z]+)\$/gc){
+                       $theirtype = 1;
+                       $theirvar = $1;
+               }
+                       # expanded var
+               unless($ourstype){
+                       if($ours =~ m/\G\$$PREFIX-([A-Za-z]+):\s+(.*?)\s\$/gc){
+                               $ourstype = 2;
+                               $oursvar = $1;
+                               $oursval = $2;
+                       }
+               }
+               unless($theirtype){
+                       if($theirs =~ m/\G\$$PREFIX-([A-Za-z]+):\s+(.*?)\s\$/gc){
+                               $theirtype = 2;
+                               $theirvar = $1;
+                               $theirval = $2;
+                       }
+               }
+                       # non-var text
+               unless($ourstype){
+                       if($ours =~ m/\G(\$?[^\x24]*)/gc){
+                               $ourstype = 3;
+                               $oursval = $1;
+                       }
+               }
+               unless($theirtype){
+                       if($theirs =~ m/\G(\$?[^\x24]*)/gc){
+                               $theirtype = 3;
+                               $theirval = $1;
+                       }
+               }
+
+               # are we done?
+               if(pos($ours)==length $ours && pos($theirs) == length $theirs){
+                       $more = 0;
+               }
+               if($ourstype == 0 && $theirtype == 0){
+                       die "NHsubst MERGE FAILED - aborted infinite loop\n";
+               }
+
+               # now see if ours and their match or can be resolved
+                       # text
+               if($ourstype == 3 && $theirtype == 3){
+                       if($oursval eq $theirval){
+                               $out .= $oursval;
+                               next;
+                       }
+                       return undef;
+               }
+               if($ourstype == 3 || $theirtype == 3){
+                       return undef;
+               }
+# XXX we could do better: on failure of one field, return 2 lines with the fields we _can_ fix
+#  substituted into those lines, leaving only the fail-to-match bits for the user to
+#  deal with.  Later.
+                       # vars (all 4 cases)
+               if($oursvar ne $theirvar){
+                       return undef;
+               }
+               my $m = merge_one_var_maybe($oursvar, $oursval, $theirval);
+               if(! defined $m){
+                       return undef;
+               }
+               $out .= $m;
+       }
+       return $out;
+}
+
+# return undef if we can't merge the values; $NAME: VALUE $ or $NAME$ (as appropriate) if we can.
+sub merge_one_var_maybe {
+       my($varname, $oursval, $theirval) = @_;
+       my $resolvedas;
+       {
+               no strict;
+               my $fn = "PREFIX::$varname";
+               if(defined &$fn){
+                       $resolvedas = &$fn($PREFIX,$varname,$oursval, $theirval);
+               } else {
+                       $resolvedas = undef;    # can't resolve
+               }
+       }
+
+       if(!defined $resolvedas){
+               $::count++;     # we have an externally visible conflict
+               return undef;
+       } else {
+               return $resolvedas;
+       }
+       # NOTREACHED
+}
+
+package PREFIX;
+# Resolve the conflict of a single var's 2 values.  Return undef to leave the conflict.
+sub Date {
+       my($PREFIX, $varname, $mine, $theirs) = @_;
+       my $m = ($mine =~ m/(\d+)/)[0];
+       my $t = ($theirs =~ m/(\d+)/)[0];
+       return undef unless ($m>0) && ($t>0);
+
+       return "\$$PREFIX-$varname: " . (($m>$t)?$mine:$theirs) .' $';
+}
+
+#sub Header {
+#sub Author {
+
+sub Branch {
+       my($PREFIX, $varname, $mine, $theirs) = @_;
+       return "\$$PREFIX-$varname: $mine \$";
+}
+
+sub Revision {
+       my($PREFIX, $varname, $mine, $theirs) = @_;
+       return "\$$PREFIX-$varname: $mine \$";
+}
+__END__
+
+TEST 1:
+<<< d1
+$TEST-Date: 1 $
+===
+$TEST-Date: 3 $
+>>> d3
+
+TEST 2:
+nothing
+at all
+
+TEST 3:
+<<< d1
+a line
+===
+one line
+two lines
+>>> d3
+
+TEST 4:
+<<< d1
+$TEST-Date: 1 $ yes
+===
+$TEST-Date: 1 $ no
+>>> d3
+
+TEST 5:
+<<< d1
+$TEST-Date: 3 $ yes
+===
+$TEST-Date: 1 $ yes
+>>> d3
+
+TEST 6:
+<<< d1
+$TEST-Date: 3 $ yes$TEST-Date: 4 $
+===
+$TEST-Date: 1 $ yes$TEST-Date: 5 $
+>>> d3
+
+TEST 7:
+<<< d1
+$TEST-Branch: mine $
+===
+$TEST-Branch: theirs $
+>>> d3
diff --git a/DEVEL/hooksdir/NHtext b/DEVEL/hooksdir/NHtext
new file mode 100755 (executable)
index 0000000..487a6f6
--- /dev/null
@@ -0,0 +1,145 @@
+#!/usr/bin/perl
+#
+# NHtext
+# $NHDT-Date$
+# clean/smudge filter for handling substitutions
+use strict;
+
+my $debug = 0;
+
+my $sink = ($^O eq "MSWin32")? "NUL" :"/dev/null";
+my $dbgfile = ($^O eq "MSWin32") ? "$ENV{TEMP}.$$" : "/tmp/trace.$$";
+open TRACE, ">>", ($debug==0)? $sink : $dbgfile;
+print TRACE "START CLIENT ARGV:\n";
+print TRACE "[0] $0\n";
+my $x1;
+for(my $x=0;$x<scalar @ARGV;$x++){
+       $x1 = $x+1;
+       print TRACE "[$x1] $ARGV[$x]\n";
+}
+print TRACE "ENV:\n";
+foreach my $k (sort keys %ENV){
+       next unless ($k =~ m/^(GIT_|NH)/);
+       print TRACE " $k => $ENV{$k}\n";
+}
+print TRACE "CWD: " . `pwd`;
+print TRACE "END\n";
+
+# pick up the prefix for substitutions in this repo
+my $PREFIX = `git config --local --get nethack.substprefix`;
+chomp($PREFIX);
+
+my $submode = 0;       # ok to make non-cleaning changes to file
+my $mode;
+
+if($ARGV[0] eq "--clean"){
+       $mode = "c";
+       if(0 == 0+$ENV{NHMODE}){
+               $submode = 1;           # do NOT add extra changes to the file
+               print TRACE "SKIPPING\n";
+       }
+} elsif($ARGV[0] eq "--smudge"){
+       $mode = "s";
+} else {
+       warn "Unknown mode '$ARGV[0]'\n";
+       exit 1;
+}
+
+# XXX for now, there isn't any - if we get called, we subst.  No options for now.
+# get relevent config info
+#XXX
+#git check-attr -a $ARGV[1]
+
+# process stdin to stdout
+
+while(<STDIN>){
+       print TRACE "IN: $_";
+       # $1 - var and value (not including trailing $)
+       # $2 - var
+       # $4 - value or undef
+#      s/\$$PREFIX-(([A-Za-z][A-Za-z0-9_]*)(: ([^\N{DOLLAR SIGN}]+))?)\s*\$/&handlevar($2,$4)/eg;
+       s/\$$PREFIX-(([A-Za-z][A-Za-z0-9_]*)(: ([^\x24]+))?)\s*\$/&handlevar($2,$4)/eg;
+       print;
+       print TRACE "OT: $_";
+}
+
+sub handlevar {
+       my($var, $val) = @_;
+
+       my $subname = "PREFIX::$var";
+       if(defined &$subname){
+               no strict;
+               $val = &$subname($val,$mode,$submode);
+       } else {
+               warn "No handler for \$$PREFIX-$var\n";
+       }
+
+       if(length $val){
+               return "\$$PREFIX-$var: $val \$";
+       } else {
+               return "\$$PREFIX-$var\$";
+       }
+}
+
+package PREFIX;
+use POSIX qw(strftime);
+
+# On push, put in the current date because we changed the file.
+# On pull, keep the current value so we can see the last change date.
+sub Date {
+       my($val, $mode, $submode) = @_;
+       if($mode eq "c"){
+               if($submode==0){
+                       # we add this to make merge easier for now XXX
+                       my $now = time; # not %s below - may not be portable
+                       # YYYY/MM/DD HH:MM:SS
+                       $val = "$now " . strftime("%Y/%m/%d %H:%M:%S", gmtime($now));
+               }
+       }
+       if($mode eq "s"){
+               $val =~ s/\s*$//;       # XXX why do I need this?
+       }
+       return $val;
+}
+
+#sub Header {
+#}
+#sub Author {
+#}
+
+# NB: the standard-ish Revision line isn't enough - you need Branch/Revision -
+#     but we split it into 2 so we can use the standard processing code on Revision
+#     and just slip Branch in.
+sub Branch {
+       my($val, $mode, $submode) = @_;
+       if($mode eq "c"){
+               if($submode==0){
+                       $val = `git branch --no-color --contains`;
+                       chomp($val);    #XXX
+                       $val =~ s/^\*\s*//;
+               }
+       }
+       if($mode eq "s"){
+#XXX do we need this now?
+               $val =~ s/\s*$//;       # XXX why do I need this?
+       }
+       return $val;
+}
+
+sub Revision {
+       my($val, $mode, $submode) = @_;
+       if($mode eq "c"){
+               if($submode==0){
+                       my $file = $ARGV[1];
+                       my @val = `git log --follow --oneline $file`;
+                       $val = sprintf("1.%d",0+$#val);
+               }
+       }
+       if($mode eq "s"){
+#XXX do we need this here?
+               $val =~ s/\s*$//;       # XXX why do I need this?
+       }
+       return $val;
+}
+
+__END__
diff --git a/DEVEL/hooksdir/TARGET b/DEVEL/hooksdir/TARGET
new file mode 100644 (file)
index 0000000..f321e28
--- /dev/null
@@ -0,0 +1 @@
+.git/hooks
diff --git a/DEVEL/hooksdir/applypatch-msg b/DEVEL/hooksdir/applypatch-msg
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/commit-msg b/DEVEL/hooksdir/commit-msg
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/post-applypatch b/DEVEL/hooksdir/post-applypatch
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/post-checkout b/DEVEL/hooksdir/post-checkout
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/post-commit b/DEVEL/hooksdir/post-commit
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/post-merge b/DEVEL/hooksdir/post-merge
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/post-rewrite b/DEVEL/hooksdir/post-rewrite
new file mode 100755 (executable)
index 0000000..e1b2524
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::saveSTDIN;
+&NHgithook::PRE;
+&NHgithook::resetSTDIN;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/pre-applypatch b/DEVEL/hooksdir/pre-applypatch
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/pre-auto-gc b/DEVEL/hooksdir/pre-auto-gc
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/pre-commit b/DEVEL/hooksdir/pre-commit
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/pre-push b/DEVEL/hooksdir/pre-push
new file mode 100755 (executable)
index 0000000..e1b2524
--- /dev/null
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::saveSTDIN;
+&NHgithook::PRE;
+&NHgithook::resetSTDIN;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/pre-rebase b/DEVEL/hooksdir/pre-rebase
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/hooksdir/prepare-commit-msg b/DEVEL/hooksdir/prepare-commit-msg
new file mode 100755 (executable)
index 0000000..b5bf990
--- /dev/null
@@ -0,0 +1,30 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+#STARTUP-START
+BEGIN {
+    # OS hackery has to be duplicated in each of the hooks :/
+    # first the directory separator
+    my $DS = quotemeta('/');
+    my $PDS = '/';
+    # msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+    # temporarily removed because inconsistent behavior
+    # if ($^O eq "msys")
+    # {
+    #   $/ = "\r\n";
+    #   $\ = "\r\n";
+    # }
+    if($^O eq "MSWin32"){
+        $DS = quotemeta('\\');
+       $PDS = '\\';
+    }
+    $gitdir = `git rev-parse --git-dir`;
+    chomp $gitdir;
+    push(@INC, $gitdir.$PDS."hooks");
+}
+use NHgithook;
+#STARTUP-END
+
+&NHgithook::PRE;
+&NHgithook::POST;
+exit 0;
diff --git a/DEVEL/nhgitset.pl b/DEVEL/nhgitset.pl
new file mode 100755 (executable)
index 0000000..183c73d
--- /dev/null
@@ -0,0 +1,317 @@
+#!/usr/bin/perl
+# $NHDT-Date$
+
+# value of nethack.setupversion we will end up with when this is done
+# version 1 is reserved for repos checked out before versioning was added
+my $version_new = 2;
+my $version_old = 0;   # current version, if any (0 is no entry ergo new repo)
+
+use Cwd;
+use Getopt::Std;
+
+# Activestate Perl doesn't include File::Spec.  Grr.
+BEGIN {
+        eval "require File::Spec::Functions";
+        if($@){
+                die <<E_O_M;
+File::Spec not found.  (If you are running ActiveState Perl please run:
+                cpan File::Spec
+and re-run this program.
+E_O_M
+        }
+        File::Spec::Functions->import;
+}
+
+exit 1 unless(getopts('nvf')); # TODO: this can probably have better output
+
+# OS hackery
+my $DS = quotemeta('/');               # Directory Separator (for regex)
+my $DSP = '/';                         # ... for printing
+#   Temporarily disabled; there's something weird about msys
+#      msys: POSIXish over a Windows filesystem (so / not \ but \r\n not \n).
+#if($^O eq "msys"){
+#      $/ = "\r\n";
+#      $\ = "\r\n";
+#      # NB: We don't need to do anything about File::Spec.  It doesn't know
+#      #     about msys but it defaults to Unix, so we'll be ok.
+#}
+if($^O eq "MSWin32"){
+       $DS = quotemeta('\\');
+       $DSP = '\\';
+}
+
+# make sure we're at the top level of a repo
+if(! -d ".git"){
+       die "This is not the top level of a git repository.\n";
+}
+
+my $vtemp = `git config --local --get nethack.setupversion`;
+chomp($vtemp);
+if($vtemp > 0){
+       $version_old = 0+$vtemp;
+       if($version_old != $version_new){
+               print STDERR "Migrating from setup version $version_old to $version_new\n" if($opt_v);
+       }
+}
+# legacy check:
+if(length $vtemp == 0){
+       if(`git config --get merge.NHsubst.name` =~ m/^Net/){
+               $version_old = 1;
+               print STDERR "Migrating to setup version 1\n" if($opt_v);
+       }
+}
+
+my $gitadddir = `git config --get nethack.gitadddir`;
+chomp($gitadddir);
+if(length $gitadddir){
+       if(! -d $gitadddir){
+               die "nethack.gitadddir has invalid value '$gitadddir'\n";
+       }
+}
+print STDERR "nethack.gitadddir=$gitadddir\n" if($opt_v);
+
+# This is (relatively) safe because we know we're at R in R/DEVEL/nhgitset.pl
+my $srcdir = ($0 =~ m!^(.*)$DS!)[0];
+
+if(! -f catfile($srcdir, 'nhgitset.pl')){
+       die "I can't find myself in '$srcdir'\n";
+}
+
+print STDERR "Copying from: $srcdir\n" if($opt_v);
+
+if($opt_f || $version_old==0){
+       print STDERR "Configuring line endings\n" if($opt_v);
+       unlink catfile('.git','index') unless($opt_n);
+       system("git reset") unless($opt_n);
+       system("git config --local core.safecrlf true") unless($opt_n);
+       system("git config --local core.autocrlf false") unless($opt_n);
+} elsif($version_old <2){
+       my $xx = `git config --get --local core.safecrlf`;
+       if($xx !~ m/true/){
+               print STDERR "\nNeed to 'rm .git${DSP}index;git reset'.\n";
+               print STDERR " When ready to proceed, re-run with -f flag.\n";
+               exit 2;
+       }
+}
+
+
+
+print STDERR "Installing aliases\n" if($opt_v);
+$addpath = catfile(curdir(),'.git','hooks','NHadd');
+&add_alias('nhadd', "!$addpath add");
+&add_alias('nhcommit', "!$addpath commit");
+
+print STDERR "Installing filter/merge\n" if($opt_v);
+
+if($^O eq "MSWin32"){
+       $cmd = '.git\\\\hooks\\\\NHtext';
+} else {
+       $cmd = catfile(curdir(),'.git','hooks','NHtext');
+}
+&add_config('filter.NHtext.clean', "$cmd --clean %f");
+&add_config('filter.NHtext.smudge', "$cmd --smudge %f");
+
+$cmd = catfile(curdir(),'.git','hooks','NHsubst');
+&add_config('merge.NHsubst.name', 'NetHack Keyword Substitution');
+&add_config('merge.NHsubst.driver', "$cmd %O %A %B %L");
+
+print STDERR "Running directories\n" if($opt_v);
+
+foreach my $dir ( glob("$srcdir$DS*") ){
+       next unless(-d $dir);
+
+       my $target = catfile($dir, 'TARGET');
+       next unless(-f $target);
+
+       open TARGET, '<', $target or die "$target: $!";
+       my $targetpath = <TARGET>;
+               # still have to eat all these line endings under msys, so instead of chomp use this:
+       $targetpath =~ s![\r\n]!!g;
+       close TARGET;
+       print STDERR "Directory $dir -> $targetpath\n" if($opt_v);
+
+       my $enddir = $dir;
+       $enddir =~ s!.*$DS!!;
+       if(! &process_override($enddir, "INSTEAD")){
+               &process_override($enddir, "PRE");
+               my $fnname = "do_dir_$enddir";
+               if(defined &$fnname){
+                       &$fnname($dir, $targetpath);
+               }
+               &process_override($enddir, "POST");
+       }
+}
+
+&check_prefix; # for variable substitution
+
+if($version_old != $version_new){
+       print STDERR "Setting version to $version_new\n" if($opt_v);
+       if(! $opt_n){
+               system("git config nethack.setupversion $version_new");
+               if($?){
+                       die "Can't set nethack.setupversion $version_new: $?,$!\n";
+               }
+       }
+}
+
+exit 0;
+
+sub process_override {
+       my($srcdir, $plname) = @_;
+       return 0 unless(length $gitadddir);
+
+       my $plpath = catfile($gitadddir, $srcdir, $plname);
+#print STDERR "   ",catfile($srcdir, $plname),"\n"; # save this for updating docs - list of overrides
+       return 0 unless(-x $plpath);
+
+       print STDERR "Running $plpath\n" if($opt_v);
+       # current directory is top of target repo
+
+       unless($opt_n){
+               system("$plpath $opt_v") and die "Callout $plpath failed: $?\n";
+       }
+       return 1;
+}
+
+sub add_alias {
+       my($name, $def) = @_;
+       &add_config("alias.$name",$def);
+}
+
+sub add_config {
+       my($name, $val) = @_;
+       system('git', 'config', '--local', $name, $val) unless($opt_n);
+}
+
+sub check_prefix {
+       my $lcl = `git config --local --get nethack.substprefix`;
+       chomp($lcl);
+       if(0==length $lcl){
+               my $other = `git config --get nethack.substprefix`;
+               chomp($other);
+               if(0==length $other){
+                       print STDERR "ERROR: nethack.substprefix is not set anywhere.  Set it and re-run.\n";
+                       exit 2;
+               } else {
+                       &add_config('nethack.substprefix', $other);
+                       print STDERR "Copying prefix '$other' to local repository.\n" if($opt_v);
+               }
+               $lcl = $other;  # for display below
+       }
+       print "\n\nUsing prefix '$lcl' - PLEASE MAKE SURE THIS IS CORRECT\n\n";
+}
+
+sub do_dir_DOTGIT {
+if(1){
+       # We are NOT going to mess with config now.
+       return;
+} else {
+       my($srcdir, $targetdir) = @_;
+#warn "do_dir_DOTGIT($srcdir, $targetdir)\n";
+       my $cname = "$srcdir/config";
+       if(-e $cname){
+               print STDERR "Appending to .git/config\n" if($opt_v);
+               open CONFIG, ">>.git/config" or die "open .git/config: $!";
+               open IN, "<", $cname or die "open $cname: $!";
+               my @data = <IN>;
+               print CONFIG @data;
+               close IN;
+               close CONFIG;
+       } else {
+               print STDERR " Nothing to add to .git/config\n" if($opt_v);
+       }
+# XXX are there other files in .git that we might want to handle?
+# So just in case:
+       for my $file ( glob("$srcdir/*") ){
+               next if( $file =~ m!.*/TARGET$! );
+               next if( $file =~ m!.*/config$! );
+               die "ERROR: no handler for $file\n";
+       }
+}
+}
+
+sub do_dir_hooksdir {
+       my($srcdir, $targetdir) = @_;
+
+       for my $path ( glob("$srcdir$DS*") ){
+
+               next if( $path =~ m!.*${DS}TARGET$! );
+
+               my $file = $path;
+
+               $file =~ s!.*$DS!!;
+
+               $file = catfile($targetdir, $file);
+
+               next if($opt_n);
+
+               open IN, "<", $path or die "Can't open $path: $!";
+               open OUT, ">", "$file" or die "Can't open $file: $!";
+               while(<IN>){
+                       print OUT;
+               }
+               close OUT;
+               close IN;
+
+               if(! -x $file){
+                       chmod 0755 ,$file;
+               }
+       }
+}
+
+__END__
+(can we change the .gitattributes syntax to include a comment character?)
+maybe [comment]  attr.c:parse_attr_line
+grr - looks like # is the comment character
+
+
+
+=head1 NAME
+
+nhgitset.pl - Setup program for NetHack git repositories
+
+=head1 SYNOPSIS
+
+ cd THE_REPO
+ [git config nethack.gitadddir GITADDDIR]
+ perl SOME_PATH/DEVEL/nhgitset.pl [-v][-n][-f]
+
+=head1 DESCRIPTION
+
+nhgitset.pl installs NetHack-specific setup after a C<git clone> (or after
+changes to the desired configuration, which are installed by re-running
+nhgitset.pl).
+
+The follwing options are available:
+
+B<-f>  Force.  Do not use this unless the program requests it.
+
+B<-n>  Make no changes.
+
+B<-v>  Verbose output.
+
+=head1 CONFIG
+
+nhgitset.pl uses the following non-standard C<git config> variables:
+
+nethack.gitadddir
+
+   DOTGIT/INSTEAD
+   DOTGIT/PRE
+   DOTGIT/POST
+   hooksdir/INSTEAD
+   hooksdir/PRE
+   hooksdir/POST
+
+nethack.setupversion
+
+nethack.substprefix
+
+
+=head1 EXIT STATUS
+
+0      Success.
+
+1      Fail.
+
+2      Intervention required.