{"thread":{"id":"2379","subject":"[PATCH] Introducing: git-cvsexportcommit","startedAt":"2005-11-07T04:57:08Z","lastAt":"2005-11-07T04:57:08Z","messageCount":1,"participants":["Martin Langhoff"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"11246","messageId":"11313394283536-git-send-email-martin@catalyst.net.nz","threadId":"2379","inReplyTo":null,"subject":"[PATCH] Introducing: git-cvsexportcommit","fromName":"Martin Langhoff","fromEmail":"martin@catalyst.net.nz","sentAt":"2005-11-07T04:57:08Z","receivedAt":"2005-11-07T04:57:08Z","isPatch":true,"sender":{"key":"martin@laptop.org","avatar":null},"body":"A script that can replay commits git into a CVS checkout. Tries to ensure the\nsanity of the operation and supports mainly manual usage.\n\nIf you are reckless enough, you can ask it to autocommit when everything has\napplied cleanly. Combined with a couple more scripts could become part of\na git2cvs gateway.\n\nShould support adds/removes and binary files.\n\nSigned-off-by: Martin Langhoff <martin@catalyst.net.nz>\n\n---\nThe script has been in use in-house for a few days now, so it seems to\nwork. Should grow a matching git-cvsexport script to drive it...\n---\n\n Documentation/git-cvsexportcommit.txt |   56 ++++++++\n Makefile                              |    2 \n git-cvsexportcommit.perl              |  225 +++++++++++++++++++++++++++++++++\n 3 files changed, 282 insertions(+), 1 deletions(-)\n create mode 100644 Documentation/git-cvsexportcommit.txt\n create mode 100755 git-cvsexportcommit.perl\n\napplies-to: faf296a197318ad6a001a672e3b585e6fe31f37b\n3c52d7ffb4ce62605a132c72170613be84b5d733\ndiff --git a/Documentation/git-cvsexportcommit.txt b/Documentation/git-cvsexportcommit.txt\nnew file mode 100644\nindex 0000000..532db66\n--- /dev/null\n+++ b/Documentation/git-cvsexportcommit.txt\n@@ -0,0 +1,56 @@\n+git-cvsexportcommit(1)\n+================\n+\n+NAME\n+----\n+git-cvsexportcommit - Export a commit to a CVS checkout\n+\n+\n+SYNOPSIS\n+--------\n+git-cvsapplycommmit.perl \n+                        [ -h ] [ -v ] [ -c ] [ -p ] [PARENTCOMMIT] COMMITID \n+\n+\n+DESCRIPTION\n+-----------\n+Exports a commit from GIT to a CVS checkout, making it easier\n+to merge patches from a git repository into a CVS repository. \n+\n+Execute it from the root of the CVS working copy. GIT_DIR must be defined. \n+\n+It does its best to do the safe thing, it will check that the files are \n+unchanged and up to date in the CVS checkout, and it will not autocommit \n+by default.\n+\n+Supports file additions, removals, and commits that affect binary files.\n+\n+If the commit is a merge commit, you must tell git-cvsapplycommit what parent\n+should the changeset be done against. \n+\n+OPTIONS\n+-------\n+\n+-c::\n+\tCommit automatically if the patch applied cleanly. It will not\n+\tcommit if any hunks fail to apply or there were other problems.\n+\n+-p::\n+\tBe pedantic (paranoid) when applying patches. Invokes patch with \n+\t--fuzz=0\n+\n+-v::\n+\tVerbose.\n+\n+Author\n+------\n+Written by Martin Langhoff <martin@catalyst.net.nz>\n+\n+Documentation\n+--------------\n+Documentation by Martin Langhoff <martin@catalyst.net.nz>\n+\n+GIT\n+---\n+Part of the gitlink:git[7] suite\n+\ndiff --git a/Makefile b/Makefile\nindex 6c01dc2..9ee1605 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -94,7 +94,7 @@ SCRIPT_SH = \\\n SCRIPT_PERL = \\\n \tgit-archimport.perl git-cvsimport.perl git-relink.perl \\\n \tgit-rename.perl git-shortlog.perl git-fmt-merge-msg.perl \\\n-\tgit-svnimport.perl git-mv.perl\n+\tgit-svnimport.perl git-mv.perl git-cvsexportcommit.perl\n \n SCRIPT_PYTHON = \\\n \tgit-merge-recursive.py\ndiff --git a/git-cvsexportcommit.perl b/git-cvsexportcommit.perl\nnew file mode 100755\nindex 0000000..7074b0c\n--- /dev/null\n+++ b/git-cvsexportcommit.perl\n@@ -0,0 +1,225 @@\n+#!/usr/bin/perl -w\n+\n+use strict;\n+use Getopt::Std;\n+use File::Temp qw(tempdir);\n+use Data::Dumper;\n+\n+unless ($ENV{GIT_DIR} && -r $ENV{GIT_DIR}){\n+    die \"GIT_DIR is not defined or is unreadable\";\n+}\n+\n+our ($opt_h, $opt_p, $opt_v, $opt_c );\n+\n+getopt('hpvc');\n+\n+$opt_h && usage();\n+\n+die \"Need at least one commit identifier!\" unless @ARGV;\n+\n+# setup a tempdir\n+our ($tmpdir, $tmpdirname) = tempdir('git-cvsapplycommit-XXXXXX',\n+\t\t\t\t     TMPDIR => 1,\n+\t\t\t\t     CLEANUP => 1);\n+\n+print Dumper(@ARGV);\n+# resolve target commit\n+my $commit;\n+$commit = pop @ARGV;\n+$commit = `git-rev-parse --verify \"$commit\"^0`;\n+chomp $commit;\n+if ($?) {\n+    die \"The commit reference $commit did not resolve!\";\n+}\n+\n+# resolve what parent we want\n+my $parent;\n+if (@ARGV) {\n+    $parent = pop @ARGV;\n+    $parent =  `git-rev-parse --verify \"$parent\"^0\"`;\n+    chomp $parent;\n+    if ($?) {\n+\tdie \"The parent reference did not resolve!\";\n+    }\n+}\n+\n+# find parents from the commit itself\n+my @commit  = `git-cat-file commit $commit`;\n+my @parents;\n+foreach my $p (@commit) {\n+    if ($p =~ m/^$/) { # end of commit headers, we're done\n+\tlast;\n+    }\n+    if ($p =~ m/^parent (\\w{40})$/) { # found a parent\n+\tpush @parents, $1;\n+    }\n+}\n+\n+if ($parent) {\n+    # double check that it's a valid parent\n+    foreach my $p (@parents) {\n+\tmy $found;\n+\tif ($p eq $parent) {\n+\t    $found = 1;\n+\t    last;\n+\t}; # found it\n+\tdie \"Did not find $parent in the parents for this commit!\";\n+s    }\n+} else { # we don't have a parent from the cmdline...\n+    if (@parents == 1) { # it's safe to get it from the commit\n+\t$parent = $parents[0];\n+    } else { # or perhaps not!\n+\tdie \"This commit has more than one parent -- please name the parent you want to use explicitly\";\n+    }\n+}\n+\n+$opt_v && print \"Applying to CVS commit $commit from parent $parent\\n\";\n+\n+# grab the commit message\n+`git-cat-file commit $commit | sed -e '1,/^\\$/d' > .msg`;\n+$? && die \"Error extraction the commit message\";\n+\n+my (@afiles, @dfiles, @mfiles);\n+my @files = `git-diff-tree -r $parent $commit`;\n+print @files;\n+$? && die \"Error in git-diff-tree\";\n+foreach my $f (@files) {\n+    chomp $f;\n+    my @fields = split(m/\\s+/, $f);\n+    if ($fields[4] eq 'A') {\n+\tpush @afiles, $fields[5];\n+    }\n+    if ($fields[4] eq 'M') {\n+\tpush @mfiles, $fields[5];\n+    }\n+    if ($fields[4] eq 'R') {\n+\tpush @dfiles, $fields[5];\n+    }\n+}\n+$opt_v && print \"The commit affects:\\n \";\n+$opt_v && print join (\"\\n \", @afiles,@mfiles,@dfiles) . \"\\n\\n\";\n+undef @files; # don't need it anymore\n+\n+# check that the files are clean and up to date according to cvs\n+my $dirty;\n+foreach my $f (@afiles, @mfiles, @dfiles) {\n+    # TODO:we need to handle removed in cvs and/or new (from git) \n+    my $status = `cvs -q status \"$f\" | grep '^File: '`;\n+\n+    unless ($status =~ m/Status: Up-to-date$/) {\n+\t$dirty = 1;\n+\twarn \"File $f not up to date in your CVS checkout!\\n\";\n+    }\n+}\n+if ($dirty) {\n+    die \"Exiting: your CVS tree is not clean for this merge.\";\n+}\n+\n+###\n+### NOTE: if you are planning to die() past this point\n+###       you MUST call cleanupcvs(@files) before die()\n+###\n+\n+\n+print \"'Patching' binary files\\n\";\n+\n+my @bfiles = `git-diff-tree -p $parent $commit | grep '^Binary'`;\n+@bfiles = map { chomp } @bfiles;\n+foreach my $f (@bfiles) {\n+    # check that the file in cvs matches the \"old\" file\n+    # extract the file to $tmpdir and comparre with cmp\n+    my $tree = `git-rev-parse $parent^{tree} `;\n+    chomp $tree;\n+    my $blob = `git-ls-tree $tree \"$f\" | cut -f 1 | cut -d ' ' -f 3`;\n+    chomp $blob;\n+    `git-cat-file blob $blob > $tmpdir/blob`;\n+    `cmp -q $f $tmpdir/blob`;\n+    if ($?) {\n+\twarn \"Binary file $f in CVS does not match parent.\\n\";\n+\t$dirty = 1;\n+\tnext;\n+    }\n+\n+    # replace with the new file\n+     `git-cat-file blob $blob > $f`;\n+\n+    # TODO: something smart with file modes\n+\n+}\n+if ($dirty) {\n+    cleanupcvs(@files);\n+    die \"Exiting: Binary files in CVS do not match parent\";\n+}\n+\n+## apply non-binary changes\n+my $fuzz = $opt_p ? 0 : 2;\n+\n+print \"Patching non-binary files\\n\";\n+print `(git-diff-tree -p $parent -p $commit | patch -p1 -F $fuzz ) 2>&1`;\n+\n+my $dirtypatch = 0;\n+if (($? >> 8) == 2) {\n+    cleanupcvs(@files);\n+    die \"Exiting: Patch reported serious trouble -- you will have to apply this patch manually\";\n+} elsif (($? >> 8) == 1) { # some hunks failed to apply\n+    $dirtypatch = 1;\n+}\n+\n+foreach my $f (@afiles) {\n+    `cvs add $f`;\n+    if ($?) {\n+\t$dirty = 1;\n+\twarn \"Failed to cvs add $f -- you may need to do it manually\";\n+    }\n+}\n+\n+foreach my $f (@dfiles) {\n+    `cvs rm -f $f`;\n+    if ($?) {\n+\t$dirty = 1;\n+\twarn \"Failed to cvs rm -f $f -- you may need to do it manually\";\n+    }\n+}\n+\n+print \"Commit to CVS\\n\";\n+my $commitfiles = join(' ', @afiles, @mfiles, @dfiles);\n+my $cmd = \"cvs commit -F .msg $commitfiles\";\n+\n+if ($dirtypatch) {\n+    print \"NOTE: One or more hunks failed to apply cleanly.\\n\";\n+    print \"Resolve the conflicts and then commit using:n\";\n+    print \"\\n    $cmd\\n\\n\";\n+    exit;\n+}\n+\n+\n+if ($opt_c) {\n+    print \"Autocommit\\n  $cmd\\n\";\n+    print `cvs commit -F .msg $commitfiles 2>&1`;\n+    if ($?) {\n+\tcleanupcvs(@files);\n+\tdie \"Exiting: The commit did not succeed\";\n+    }\n+    print \"Committed successfully to CVS\\n\";\n+} else {\n+    print \"Ready for you to commit, just run:\\n\\n   $cmd\\n\";\n+}\n+sub usage {\n+\tprint STDERR <<END;\n+Usage: GIT_DIR=/path/to/.gi ${\\basename $0}      # fetch/update GIT from CVS\n+       [-h] [-p] [ parent ] commit\n+END\n+\texit(1);\n+}\n+\n+# ensure cvs is clean before we die\n+sub cleanupcvs {\n+    my @files = @_;\n+    foreach my $f (@files) {\n+\t`cvs -q update -C \"$f\"`;\n+\tif ($?) {\n+\t    warn \"Warning! Failed to cleanup state of $f\\n\";\n+\t}\n+    }\n+}\n+\n---\n0.99.8.GIT\n"}]}