{"thread":{"id":"7990","subject":"Yet another Perforce importer","startedAt":"2007-05-05T22:48:05Z","lastAt":"2007-05-31T09:23:51Z","messageCount":4,"participants":["Alex Riesen"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"41165","messageId":"20070505224805.GD2898@steel.home","threadId":"7990","inReplyTo":null,"subject":"Yet another Perforce importer","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2007-05-05T22:48:05Z","receivedAt":"2007-05-05T22:48:05Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"I was unhappy with the existing importer because it did not allow to\ncommit the current state of a client, and did not allow to find out\nhow this client state was produced. This script does this and also\nallows for updating git repo incrementally (so that the files which\n\"officially\" did not change will not be reupdated. Active by default,\nbut can be switched off to do a complete import, see --full).\n\nIt is a perl script again, sorry. This one is developed and tested\nwith ActiveState Perl (which explains the binmode after each and every\nopen), so use with caution. If you're lucky to use Perforce in a POSIX\nenvironment - remove the binmodes and windows-related hack to detect\ncase-sensitivity problems in filenames. I have no idea what would they\nbreak there.\n\nThe script has some provisions for Windows Maloperating System:\nit tries to figure out the conflicts caused by disability in its\nfilesystems with regard to case insensitivity. It is enabled\nautomatically for ActiveState Perl. If you see warnings about some\nconflicts it usually means that the state in the working directory\ncannot be trusted anymore (and is a fairly typical error in\nenvironments contaminated with windows).  The script does not reread\nthe files from p4 server (I admit being lazy), it just trusts the\nworking directory and will do in such a case an untrustworthy commit.\n(the reason behind this is that at work, where I have to use perforce,\nI had to give up arguing with everyone about this problem. The people\nwho could do something about it just don't care about the fact that\nthe stupid thing they've chosen has very non-windows rules regarding\nfilenames. Either they are deaf, or dumb, or both).\n\nThe client definition, the state and the information how you got the\nstate are saved in a side branch (refs/p4import/<client-name>), which\nreferences the previous imported state (if any) and the git commit\njust made (if --yes passed). The result looks funny in gitk but works\ngood enough for incremental imports (see the first while in git_p4_init).\n\n\"The information how you got the client state\" must be given and is\nexpected to be a file which will be stored on the side branch under\nthe name \"spec\". The story behind this is that Perforce was so bad for\nthe workflow we have here at work, that direct use of the system is\ndiscouraged (like in \"made impossible\") and IT&Tooling Department\n\"provided\" us with a program which can take a configuration file which\nhas mapping and revision information. So now you can bind the history\nof a project in one place: the linear history of this configuration\nfile. Good idea, if you're stuck with Perforce (and good job by\nPerforce to have us stuck with them), if implemented rightly. It\nwasn't of course, but this is another long and depressing story.\nRemote-to-local mapping and the revisions of files are stored in \"have\",\nand the client definition - in \"client\" on that side branch.\n\nBeing work in progress, the script does not commit anything by\ndefault (use -y|--yes to commit), just updates the index.\n\nProbably, wont work without initial commit. Can't remember testing\nthat.\n\nDoes not touch your Perforce client (except for reading). Don't forget\nto log in, though. I found that environment variables (P4SERVER or\nP4PASSWD) sometimes work (they stopped recently).\n\nExpects to find P4 and Git tools in path (p4 and git, specifically).\n\nNot tested in POSIX environment (I'm not allowed to have any at work,\nand I promise to kill anyone who tries to install Perforce in my home\nnetwork).\n\nQuite memory hungry. Slow (but Git works so much faster than perforce\nso I really couldn't care less: I sync with central server one-two\ntimes a day).\n\nOn windows: will have problems with long pathnames (can break on w2k,\ntry using wxp).\n\nOn windows: avoid Ctrl-C or closing the console window while the\nscript is running.  Interrupted script shouldn't corrupt anything but\nthe act can take your whole system down (happened to me twice or\ntrice).\n\nThe script:\n\n\n#!/usr/bin/perl -w\n\nlocal $VERBOSE = 0;\nlocal $DRYRUN = 0;\nlocal $AUTO_COMMIT = 0;\nlocal $JUST_COMMIT = 0;\nlocal $P4CLIENT = undef;\nlocal @EDIT_COMMIT = 0;\nlocal @FULL_IMPORT = 0;\nlocal @DESC = ();\nlocal $SPEC = undef;\nlocal @P4ARGS = ();\nlocal $P4HAVE_FILE = undef;\npush(@P4ARGS, '-P', $ENV{P4PASSWD})\n    if defined($ENV{P4PASSWD}) and length($ENV{P4PASSWD});\nuse Cwd;\nlocal $start_dir = cwd();\n\nsub read_args {\n    my ($in_client, $in_cl, $in_fi, $in_p4) = (0,0,0,0);\n    foreach my $f ( @_ ) {\n\tif ($in_client) { $in_client = 0; $P4CLIENT = $f; next }\n\tif ($in_cl) { $in_cl=0; push(@DESC,\"c$f\"); next }\n\tif ($in_fi) { $in_fi=0; push(@DESC,\"f$f\"); next }\n\tif ($in_p4) { $in_p4=0; push(@DESC,\"4$f\"); next }\n\t$DRYRUN=1, next if $f eq '-n' or $f eq '--dry-run';\n\t$AUTO_COMMIT=1, next if $f eq '-y' or $f eq '--yes';\n\t$JUST_COMMIT=1, next if $f eq '--just-commit';\n\t$EDIT_COMMIT=1, next if ($f eq '-e') or ($f eq '--edit');\n\t$FULL_IMPORT=1, next if $f eq '--full';\n\t$VERBOSE++, next if $f eq '-v' or $f eq '--verbose';\n\t$in_client = 1, next if $f eq '--client';\n\t$in_cl = 1, next if $f eq '-C';\n\t$in_fi = 1, next if $f eq '-F';\n\t$in_p4 = 1, next if ($f eq '--ptr') or ($f eq '--p4');\n\tif ($f eq '--help' or $f eq '-h') {\n\t    print <<EOF;\n$0 <specification> [-n|--dry-run] [-y|--yes] [--client <client-name>] \\\n[-e|--edit] [--just-commit] [--full] [-v|--verbose] [-C <change-number>] \\\n[-F <filename>] [--ptr|--p4 <p4-path-and/or-revision>]\n\nPerforce client state importer. Creates a git commit on the current\nbranch from a state the given p4 client and working directory hold.\n\n<specification> must be given and is expected to be a file which will be\nstored on the side branch under the name \"spec\".\nRemote-to-local mapping and the revisions of files are stored in \"have\",\nand the client definition - in \"client\".\n\n--client client\tSpecify client name (saved in .git/p4/client for the next time)\n--full\t\tPerform full import, don't even try to figure out what changed\n-y|--yes\tCommit automatically (by default only index updated)\n--just-commit\tTo be used after you forgot to run with --yes first time\n-n|--dry-run\tDo not update the index and do not commit\n-e|--edit\tEdit commit description before committing\n-v|--verbose\tBe more verbose. Can be given many times, increases verbosity\n-F file\t\tTake description for the commit from a file in the\n\t\tnext parameter\n-C change\tTake description for the commit from this p4 change\n--p4|--ptr p4-path-and/or-revision Take description for the commit from the p4\n\t\tchange described by this p4 path, possibly including revision\n\t\tspecification\n\nThe descriptions taken from p4 changes given by -C and --p4 will\nbe concatenated if the options given multiple times.\n\nEOF\n\t    exit(0);\n\t}\n\twarn(\"$0: spec already set, $f ignored\\n\"),next if defined($SPEC);\n\t$SPEC = $f;\n    }\n}\nread_args(@ARGV);\n\nlocal ($GIT_DIR) = qx{git rev-parse --git-dir};\n$GIT_DIR =~ s/\\r?\\n$//s if defined($GIT_DIR);\ndie \"$0: git directory not found\\n\" if !defined($GIT_DIR) or !-d $GIT_DIR;\n$ENV{VISUAL} = 'vim' unless defined($ENV{VISUAL});\nlocal $editor = $ENV{VISUAL};\n$editor = $ENV{EDITOR} unless defined($editor);\ndie \"$0: no editor defined\\n\" unless defined($editor);\n# P4 client was given in command-line. Store it\nif ( defined($P4CLIENT) ) {\n    mkdir \"$GIT_DIR/p4\", 0777;\n    if ( open(F, '>', \"$GIT_DIR/p4/client\") ) {\n\tprint F \"$P4CLIENT\\n\";\n\tclose(F);\n    } else {\n\tdie \"$0: cannot store client name: $!\\n\"\n    }\n} else {\n    if ( open(F, '<', \"$GIT_DIR/p4/client\") ) {\n\t($P4CLIENT) = <F>;\n\tclose(F);\n\t$P4CLIENT =~ s/^\\s*//,$P4CLIENT =~ s/\\s*$// if defined($P4CLIENT);\n    }\n}\ndie \"P4 client not defined\\n\" if !defined($P4CLIENT) or !length($P4CLIENT);\nprint \"reading P4 client $P4CLIENT\\n\" if $VERBOSE;\nlocal ($P4ROOT, $p4clnt, $P4HOST);\nopen(my $fdo, '>', \"$GIT_DIR/p4/client.def\") or die \"p4/client.def: $!\\n\";\nbinmode($fdo);\nopen(my $fdi, '-|', \"p4 client -o $P4CLIENT\") or die \"p4 client: $!\\n\";\nbinmode($fdi);\nmy $last_line_len = 0;\nwhile (<$fdi>) {\n    next if /^#/o;\n    if ( m/^\\s*Root:\\s*(\\S+)[\\\\\\/]*\\s*$/so ) { $P4ROOT = $1 }\n    elsif ( m/^\\s*Client:\\s*(\\S+)/o ) { $p4clnt = $1 }\n    elsif ( m/^\\s*Host:\\s*(\\S+)/o ) { $P4HOST = $1 }\n    ($VERBOSE and print), next if /^(Access|Update):/;\n    s/\\r?\\n$//so;\n    my $len = length($_);\n    print $fdo \"$_\\n\" if $len or $len != $last_line_len;\n    $last_line_len = $len;\n}\nclose($fdi);\nclose($fdo);\n\ndie \"Client root not defined\\n\" unless defined($P4ROOT);\nif ( $VERBOSE ) {\n    print \"GIT_DIR: $GIT_DIR\\n\";\n    print \"Root: $P4ROOT (cwd: $start_dir)\\n\";\n    print \"Host: $P4HOST\\n\";\n    print \"Client: $p4clnt\\n\" if $p4clnt ne $P4CLIENT;\n}\nmy ($git_head,$git_p4_head,$git_p4_have) = &git_p4_init;\n\nif ($JUST_COMMIT) {\n    git_p4_commit($git_head, $git_p4_head);\n    exit 0;\n}\n\nlocal %gitignore_dirs = ();\n$gitignore_dirs{'/'} = read_filter_file(\"$GIT_DIR/info/exclude\");\npush(@{$gitignore_dirs{'/'}}, @{read_filter_file('.gitignore')});\n\nmy %git_index = ();\n$/ = \"\\0\";\nmy @git_X = ();\nprint \"Reading git file list(git ls-files @git_X --cached -z)...\\n\" if $VERBOSE;\nforeach ( qx{git ls-files @git_X --cached -z} ) {\n    chop; # chop \\0\n    next if m/^\\.gitignore$/o;\n    next if m/\\/\\.gitignore$/o;\n    next if filtered($_);\n    $git_index{$_} = 1;\n}\n\nmy @git_add = ();\nmy @git_addx = ();\nmy @git_del = ();\nmy @git_upd = ();\n\nprint \"Reading P4 file list...\\n\" if $VERBOSE;\nlocal ($Conflicts,$Ignored,$Added,$Deleted,$Updated) = (0,0,0,0,0);\n$/ = \"\\n\";\nmy $in_name = 0;\nmy @root = split(/[\\/\\\\]+/, $P4ROOT);\nmy %p4_index = ();\nmy %p4_a_lc = ();\nmy %lnames = ();\nmy %lconflicts = ();\nif (opendir(DIR, '.')) {\n    $lnames{'.'} = [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n    closedir(DIR);\n    #print \"read $start_dir (\",scalar(@{$lnames{'.'}}),\")\\n\";\n}\nopen(my $have, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have |\") or\n    die \"$0: failed to start p4: $!\\n\";\nbinmode($have);\n$P4HAVE_FILE = \"$GIT_DIR/p4/have\";\nopen(my $storedhave, '>', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\nbinmode($storedhave);\nmy $ent;\nwhile (defined($ent=read_pydict_entry($have))) {\n    next if !defined($ent->{depotFile}) or !defined($ent->{clientFile});\n    my $a = $ent->{depotFile};\n    $ent->{clientFile} =~ m!^//[^/]+/(.*)!o;\n    my $b = $1;\n    my @bb = split(/\\/+/, $b);\n    print $storedhave \"$a\\0$ent->{clientFile}\\0$ent->{haveRev}\\0\\n\";\n\n    if ( $^O eq 'MSWin32' ) {\n\t# stupid windows, daft activestate, dumb P4\n\t# This piece below is checking for file name conflicts\n\t# which happen on windows because of it mangling the names.\n\tmy $blc = lc $b;\n\tif ( $#bb > 0 ) {\n\t    my $path = '.';\n\t    foreach my $n (@bb[0 .. $#bb -1]) {\n\t\tmy @conflicts =\n\t\t    grep {lc $_ eq lc $n and $_ ne $n} @{$lnames{$path}};\n\t\tif (@conflicts and !exists($lconflicts{\"$path/$n\"})) {\n\t\t    warn \"warning: $a -> $b\\n\".\n\t\t\t \"warning: conflict between path \\\"$path/$n\\\" and \".\n\t\t\t \"local filesystem in \\\"@conflicts\\\"\\n\";\n\t\t    $Conflicts++;\n\t\t    $lconflicts{\"$path/$n\"} = 1;\n\t\t}\n\t\t$path .= \"/$n\";\n\t\tif (!exists($lnames{$path})) {\n\t\t    if (opendir(DIR, $path)) {\n\t\t\t$lnames{$path} =\n\t\t\t    [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n\t\t\tclosedir(DIR);\n\t\t\t#print \"read $path (\",scalar(@{$lnames{$path}}),\")\\n\";\n\t\t    }\n\t\t}\n\t    }\n\t}\n\tif (!exists($p4_a_lc{$blc})) {\n\t    $p4_a_lc{$blc} = [$a, $b];\n\t} else {\n\t    warn(\"warning: $a -> $b\\n\".\n\t\t \"warning: conflicts with \".\n\t\t $p4_a_lc{$blc}->[0].\" -> \".\n\t\t $p4_a_lc{$blc}->[1].\"\\n\");\n\t    $Conflicts++;\n\t    next;\n\t}\n    }\n\n    my $i;\n    for ($i = 0; $i < $#bb; ++$i) {\n\tmy $bdir = join('/',@bb[0 .. $i]) . '/';\n\tif ( !exists($gitignore_dirs{$bdir}) ) {\n\t    $gitignore_dirs{$bdir} = read_filter_file(\"$bdir.gitignore\");\n\t}\n    }\n    if (filtered($b)) {\n\tprint \" i $b\\n\" if $VERBOSE > 3;\n\t$Ignored++;\n\tnext\n    }\n    $p4_index{$b} = $a;\n    if ( exists($git_index{$b}) ) {\n\tmy $needup = 1;\n\tif (defined($git_p4_have)) {\n\t    $prev = $git_p4_have->{$a};\n\t    if (defined($prev)) {\n\t\t$prev->[0] =~ m!^//[^/]+/(.*)!o;\n\t\t$needup = 0 if ($b eq $1) and ($prev->[1] eq $ent->{haveRev});\n\t\tif ($needup and $VERBOSE > 1) {\n\t\t    my $reason;\n\t\t    $reason = 'local file' if $b ne $1;\n\t\t    $reason = 'revision' if $prev->[1] ne $ent->{haveRev};\n\t\t    print \"$a ($reason changed)\\n\";\n\t\t}\n\t    }\n\t}\n\tif ($needup) {\n\t    $Updated++;\n\t    push(@git_upd, $b);\n\t}\n    } else {\n\t$Added++;\n\tif ( $b =~ m/\\.(bat|cmd|pl|sh|exe|dll)$/io )\n\t{ push(@git_addx, $b) } else { push(@git_add, $b) }\n    }\n}\nclose($storedhave);\nclose($have);\nundef %p4_a_lc;\n\n@git_del = grep { !exists($p4_index{$_}) } keys %git_index;\n$Deleted = $#git_del + 1;\n\n#foreach (keys %git_index)\n#{ push(@git_del, $_) if !exists($p4_index{$_}) }\n\nif ( $DRYRUN ) {\n    print($#git_add+$#git_addx+ 2,\" files to add\\n\") if $VERBOSE;\n    print map {\" a $_\\n\"} @git_add if $VERBOSE > 2;\n    print map {\" a $_\\n\"} @git_addx if $VERBOSE > 2;\n    print($#git_del+1,\" files to unreg\\n\") if $VERBOSE;\n    print map {\" d $_\\n\"} @git_del if $VERBOSE > 2;\n    print($#git_upd+1,\" files to update\\n\") if $VERBOSE;\n    print map {\" u $_\\n\"} @git_upd if $VERBOSE > 2;\n    print \"added: $Added, unregd: $Deleted, updated: $Updated, ignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n} else {\n    if (@git_add || @git_addx) {\n\tprint($#git_add+$#git_addx+ 2,\n\t      \" files | git update-index --add -z --stdin\\n\")\n\t    if $VERBOSE;\n\tif (@git_add) {\n\t    open(GIT, '| git update-index --add --chmod=-x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_add;\n\t    close(GIT);\n\t}\n\tif (@git_addx) {\n\t    open(GIT, '| git update-index --add --chmod=+x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_addx;\n\t    close(GIT);\n\t}\n    }\n\n    if (@git_del) {\n\tprint($#git_del+1,\" files | git update-index --remove -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index --force-remove -z --stdin') or\n\t    die \"$0 git-update-index(del): $!\\n\";\n\tprint GIT map {print \" d $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_del;\n\tclose(GIT);\n    }\n\n    if (@git_upd) {\n\tprint($#git_upd+1,\" files | git update-index -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index -z --stdin') or\n\t    die \"$0 git-update-index(upd): $!\\n\";\n\tprint GIT map {print \" u $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_upd;\n\tclose(GIT);\n    }\n    \n    print \"added: $Added, unregd: $Deleted, updated: $Updated, ignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n    git_p4_commit($git_head, $git_p4_head) if $AUTO_COMMIT;\n}\n\nexit 0;\n\nsub filtered {\n    my $name = shift;\n    study($name);\n    my @path = split(/\\/+/o, $name);\n    my $dir = '';\n    $name = '';\n    \n    foreach my $d (@path) {\n\t$name .= $d;\n#\tprint STDERR \"$dir: $name $d\\n\" if $v;\n\tforeach my $re (@{$gitignore_dirs{'/'}}) {\n\t    return 1 if $name =~ m/$re/;\n\t    return 1 if $d =~ m/$re/;\n\t}\n\tif ( length($dir) and exists($gitignore_dirs{$dir}) ) {\n\t    foreach my $re (@{$gitignore_dirs{$dir}}) {\n\t\treturn 1 if $name =~ m/$re/;\n\t\treturn 1 if $d =~ m/$re/;\n\t    }\n\t}\n\t$name .= '/';\n\t$dir = $name;\n    }\n#    print STDERR \"$name not filtered\\n\" if $v;\n    return 0;\n}\n\nsub read_filter_file {\n    my @filts = ();\n    my $file = shift;\n    if ( open(my $if, '<', $file) ) {\n\tprint \"added ignore file $file\\n\" if $VERBOSE;\n\t$/ = \"\\n\";\n\twhile (my $l = <$if>) {\n\t    next if $l =~ /^\\s*#/o;\n\t    next if $l =~ /^\\s*$/o;\n\t    $l =~ s/[\\r\\n]+$//so;\n\t    $l =~ s/\\./\\\\./go;\n\t    $l =~ s/\\*/.*/go;\n\t    if ( $l =~ m/\\// ) {\n\t\t$l = \"^$l($|/)\";\n\t    } else {\n\t\t$l = \"(^|/)$l\\$\";\n\t    }\n\t    print \" filter $l\\n\" if $VERBOSE > 1;\n\t    push(@filts, qr/$l/);\n\t}\n\tclose($if);\n    }\n    return \\@filts;\n}\n\nsub r_pystr\n{\n    my $fd = shift;\n    my ($len,$str)=('','');\n    my ($c,$rd,$b) = (4,0,'');\n    while ($c > 0) {\n\t$rd = sysread($fd,$b,$c);\n\twarn(\"failed to read len: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data for len\"), return undef if !$rd;\n\t$len .= $b;\n\t$c -= $rd;\n    }\n    $len = unpack('V',$len);\n    while ($len > 0) {\n\t$rd = sysread($fd,$b,$len);\n\twarn(\"failed to read data: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data\"), return undef if !$rd;\n\t$str .= $b;\n\t$len -= $rd;\n    }\n    return $str;\n}\n\nsub read_pydict_entry\n{\n    my $f = shift;\n    my ($buf,$rd);\n    FIL: while (1) {\n\t# object type identifier\n\t$rd = sysread($f, $buf, 1);\n\tlast FIL if $rd == 0;\n\twarn(\"object type: $!\\n\"),last if $rd != 1;\n\t# '{' is a python marshalled dict\n\twarn(\"object type: not {\\n\"),last if $buf ne '{';\n\tmy $ent = {};\n\tPAIR: while (1) {\n\t    my ($b,$key);\n\t    # key type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"key type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$key = r_pystr($f);\n\t\twarn(\"key: $!\\n\"),last FIL if !defined($b);\n\t    } elsif ($b eq '0') { # NULL-element, end of entry\n\t\tlast PAIR;\n\t    } else {\n\t\twarn(\"key type: not s\");\n\t\tlast FIL;\n\t    }\n\t    # value type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"$key value type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$b = r_pystr($f);\n\t\twarn(\"$key value: $!\"),last FIL if !defined($b);\n\t\t$ent->{$key} = $b;\n\t    } else {\n\t\twarn(\"$key value type: not s ($b)\");\n\t\tlast FIL;\n\t    }\n\t}\n\treturn $ent;\n    }\n    return undef;\n}\n\nsub cl2msg {\n    my $cl = shift;\n    my($o1,$o2,$i);\n    if(!open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\twarn \"p4/msg: $!\\n\";\n\treturn;\n    }\n    binmode($o1);\n    if(!open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\twarn \"p4/p4msg: $!\\n\";\n\tclose($o1);\n\treturn\n    }\n    binmode($o2);\n    if(!open($i, '-|', \"p4 describe -s $cl\")){\n\twarn \"p4 describe: $!\\n\";\n\tclose($o1);\n\tclose($o2);\n\treturn\n    }\n    binmode($i);\n    print $o1 \"$cl: \";\n    print $o2 \"$cl: \";\n    my @a;\n    while (my $l = <$i>) {\n\tlast if $l =~ /^\\s*Affected files \\.{3}\\s*$/so;\n\t$l =~ s/\\r?\\n$//so;\n\tpush @a, $l;\n    }\n    close($i);\n    print $o2 substr($a[2],1),\"\\n\";\n    close($o2);\n    print $o1 map {\"$_\\n\"} (substr($a[2],1),\"\\n\",@a);\n    close($o1);\n}\n\nsub git_p4_init {\n    my ($commit,$parent,$p4commit,$p4parent);\n    my ($HEAD, $p4head) = qx{git rev-parse HEAD refs/p4import/$P4CLIENT};\n    $HEAD = $p4head = '' if $?;\n    s/\\r?\\n//gs for ($HEAD, $p4head);\n    if (length($p4head)) {\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n    } else {\n\t$commit = $p4parent = '';\n    }\n    while (($commit ne $HEAD) and length($p4parent)) {\n\t$p4head = $p4parent;\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\tif ($VERBOSE and $HEAD eq $commit) {\n\t    print \"found p4 import commit \";\n\t    system('git','name-rev',$p4head);\n\t}\n    }\n    warn \"Current HEAD was not imported from $P4CLIENT\\n\" if $HEAD ne $commit;\n    my $p4have = undef;\n    if (!$FULL_IMPORT and $HEAD eq $commit) {\n\tif (open(my $f, '-|', \"git cat-file -p $p4head:have\")) {\n\t    my $old = $/;\n\t    $/ = \"\\0\";\n\t    my $cnt = 0;\n\t    while(1) {\n\t\tmy $p4name = <$f>;\n\t\tlast if !defined($p4name);\n\t\t$p4name =~ s/^.//so if $cnt; # remove \\n\n\t\tmy $name = <$f>;\n\t\tmy $rev = <$f>;\n\t\tlast if !defined($name) or !defined($rev);\n\t\tchop($p4name,$name,$rev);\n\t\t++$cnt;\n\t\tif (defined($p4have)) {\n\t\t    $p4have->{$p4name} = [$name,$rev];\n\t\t} else {\n\t\t    $p4have = {$p4name=>[$name,$rev]};\n\t\t}\n\t    }\n\t    $/ = $old;\n\t    close($f);\n\t    print \"loaded $cnt revisions from $p4head\\n\" if $VERBOSE;\n\t}\n    }\n    return ($HEAD, $p4head, $p4have);\n}\n\nsub git_p4_commit {\n    my ($HEAD, $p4head) = @_;\n    my ($commit,$parent,$p4commit,$p4parent);\n\n    my ($fdo,$fdi,$rc);\n    $rc = system('git','diff-index','--exit-code','--quiet','--cached','HEAD');\n    if ($rc == 0) {\n\twarn(\"No changes\\n\");\n\treturn;\n    }\n\n    return if $DRYRUN;\n\n    my $p4x = \"$GIT_DIR/p4/idx.tmp\";\n    unlink($p4x);\n    my $oldidx = $ENV{GIT_INDEX_FILE};\n\n    $ENV{PAGER} = 'cat';\n    $ENV{GIT_INDEX_FILE} = $p4x;\n\n    open(STDIN, '<', $SPEC) or die \"$SPEC: $!\\n\";\n    my ($p4spec) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to store $SPEC in git repo\\n\" if $?;\n\n    open(STDIN, '<', \"$GIT_DIR/p4/client.def\") or die \"cldef: $!\\n\";\n    my ($p4clnt) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save mappings of $P4CLIENT in git repo\" if $?;\n\n    if (!defined($P4HAVE_FILE)) {\n\tprint \"reading state of $P4CLIENT\\n\" if $VERBOSE;\n\t$P4HAVE_FILE = \"$GIT_DIR/p4/have\"; \n\topen($fdo, '>', $P4HAVE_FILE) or die \"p4/have: $!\\n\";\n\tbinmode($fdo);\n\topen($fdi, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have|\") or\n\t    die \"p4 have: $!\\n\";\n\tbinmode($fdi);\n\tmy $ent;\n\twhile (defined($ent=read_pydict_entry($fdi))) {\n\t    next if !defined($ent->{depotFile});\n\t    next if !defined($ent->{clientFile});\n\t    print $fdo \"$ent->{depotFile}\\0\",\n\t\t       \"$ent->{clientFile}\\0\",\n\t\t       \"$ent->{haveRev}\\0\\n\";\n\t}\n\tclose($fdi);\n\tclose($fdo);\n    }\n\n    open(STDIN, '<', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\n    my ($p4have) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save state of $P4CLIENT in git repo\" if $?;\n\n    unlink(\"$GIT_DIR/p4/msg\", \"$GIT_DIR/p4/p4msg\");\n    foreach my $i (@DESC) {\n\t$i =~ s/^(.)//o;\n\tif ('c' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    cl2msg($i);\n\t} elsif ('f' eq $1) {\n\t    my($o1,$o2,$i);\n\t    if (open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\t\tif (open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\t\t    if (open($i, '<', $i)) {\n\t\t\tmy $n = 0;\n\t\t\twhile(<$i>) {\n\t\t\t    $n++;\n\t\t\t    print $o1 $_;\n\t\t\t    print $o2 $_ if $n == 1;\n\t\t\t}\n\t\t\tclose($i);\n\t\t    }\n\t\t    close($o2);\n\t\t}\n\t\tclose($o1);\n\t    }\n\t} elsif ('4' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    my ($change)=qx{p4 changes -m1 $i};\n\t    if (!defined($change) or $change !~ m/\\s+(\\d+)\\s/) {\n\t\tdie \"$i does not resolve into a change number\\n\";\n\t    }\n\t    cl2msg($1);\n\t}\n    }\n    system($editor, \"$GIT_DIR/p4/msg\") if $EDIT_COMMIT;\n\n    #\n    # Store the imported file data\n    #\n    if (defined($oldidx)) { $ENV{GIT_INDEX_FILE} = $oldidx }\n    else { delete $ENV{GIT_INDEX_FILE} }\n\n    if ( $^O eq 'MSWin32' ) { open(STDERR, \"NUL\") }\n    else { open(STDERR, \"/dev/null\") }\n\n    my ($tree) = qx{git write-tree};\n    die \"Failed to write current tree\\n\" if $?;\n    $parent = length($HEAD) ? \"-p $HEAD\": '';\n    open(STDIN, '<', \"$GIT_DIR/p4/msg\") or die \"p4/msg: $!\\n\";\n    $tree =~ s/\\r?\\n//gs;\n    ($commit)=qx{git commit-tree $tree $parent};\n    die \"failed to commit current tree\\n\" if $?;\n    s/\\r?\\n//gs for ($commit);\n\n    #\n    # Storing import control data\n    #\n    $ENV{GIT_INDEX_FILE} = $p4x;\n    open($fdo, '|-', 'git update-index --add --index-info') or\n\tdie \"could not start git update-index\\n\";\n    binmode($fdo);\n    s/\\r?\\n//gs for ($p4spec,$p4clnt,$p4have);\n    print $fdo \"100644 $p4spec\\tspec\\n\";\n    print $fdo \"100644 $p4clnt\\tclient\\n\";\n    print $fdo \"100644 $p4have\\thave\\n\";\n    close($fdo);\n    if($?) {\n\tdie \"Failed to store $SPEC in p4import index and git repo\\n\".\n\t    \"Failed to save mappings of $P4CLIENT in p4import index and git repo\\n\".\n\t    \"Failed to save state of $P4CLIENT in p4import index and git repo\\n\"\n    }\n    my ($p4tree)=qx{git write-tree};\n    die \"Failed to store $SPEC (tree) in git repo\\n\" if $?;\n\n    # Bind import control data to the file data\n    $p4parent=\"-p $commit\";\n    $p4parent=\"$p4parent -p $p4head\" if length($p4head);\n    open(STDIN, '<', \"$GIT_DIR/p4/p4msg\") or die \"p4/p4msg: $!\\n\";\n    $p4tree =~ s/\\r?\\n//gs;\n    ($p4commit)=qx{git commit-tree $p4tree $p4parent};\n    die \"Failed to store $SPEC (commit) in git repo\\n\" if $?;\n    $p4commit =~ s/\\r?\\n//gs;\n\n    # Finishing touches: update references\n    $rc = system('git','update-ref','-m','data of p4import','HEAD',$commit);\n    die \"Failed to update HEAD\\n\" if $rc;\n    $rc = system('git','update-ref','-m','p4import',\"refs/p4import/$P4CLIENT\",$p4commit);\n    die \"Failed to store $SPEC (reference) in git repo\\n\" if $rc;\n\n    print STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev refs/p4import/$P4CLIENT}), \":\\n\";\n    system('git','log','--max-count=1','--pretty=format:%h %s%n',$p4commit);\n    print STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev HEAD}), \":\\n\";\n    system('git','log','--max-count=1','--pretty=format:%h %s%n',$commit);\n}\n"},{"id":"41741","messageId":"81b0412b0705100637g1072f5c7y54f781b67a569690@mail.gmail.com","threadId":"7990","inReplyTo":"20070505224805.GD2898@steel.home","subject":"Re: Yet another Perforce importer","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2007-05-10T13:37:18Z","receivedAt":"2007-05-10T13:37:18Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"Updated. A very bad bug fixed (the old script cut the imported\nhistory from the previous history on first import).\nAnd I'm sorry for txt (and for .bat).\n\n\n@rem = 'NT: CMD.EXE vim: syntax=perl noet sw=4\n@perl -x -s %0 -- %*\n@exit\n@rem ';\n#!perl -w\n#line 7\n\nlocal $VERBOSE = 0;\nlocal $DRYRUN = 0;\nlocal $AUTO_COMMIT = 0;\nlocal $JUST_COMMIT = 0;\nlocal $P4CLIENT = undef;\nlocal @EDIT_COMMIT = 0;\nlocal @FULL_IMPORT = 0;\nlocal @DESC = ();\nlocal $SPEC = undef;\nlocal @P4ARGS = ();\nlocal $P4HAVE_FILE = undef;\nlocal %P4USERS = ();\nlocal $FULL_DESC = 1;\npush(@P4ARGS, '-P', $ENV{P4PASSWD})\n    if defined($ENV{P4PASSWD}) and length($ENV{P4PASSWD});\nuse Cwd;\nlocal $start_dir = cwd();\n\nsub read_args {\n    my ($in_client, $in_cl, $in_fi, $in_p4) = (0,0,0,0);\n    foreach my $f ( @_ ) {\n\tif ($in_client) { $in_client = 0; $P4CLIENT = $f; next }\n\tif ($in_cl) { $in_cl=0; push(@DESC,\"c$f\"); next }\n\tif ($in_fi) { $in_fi=0; push(@DESC,\"f$f\"); next }\n\tif ($in_p4) { $in_p4=0; push(@DESC,\"4$f\"); next }\n\t$DRYRUN=1, next if $f eq '-n' or $f eq '--dry-run';\n\t$AUTO_COMMIT=1, next if $f eq '-y' or $f eq '--yes';\n\t$JUST_COMMIT=1, next if $f eq '--just-commit';\n\t$EDIT_COMMIT=1, next if ($f eq '-e') or ($f eq '--edit');\n\t$FULL_IMPORT=1, next if $f eq '--full';\n\t$FULL_DESC++, next if $f eq '--p4-desc';\n\t$VERBOSE++, next if $f eq '-v' or $f eq '--verbose';\n\t$in_client = 1, next if $f eq '--client';\n\t$in_cl = 1, next if $f eq '-C';\n\t$in_fi = 1, next if $f eq '-F';\n\t$in_p4 = 1, next if ($f eq '--ptr') or ($f eq '--p4');\n\tif ($f eq '--help' or $f eq '-h') {\n\t    print <<EOF;\n$0 <specification> [-n|--dry-run] [-y|--yes] [--client <client-name>] \\\n[-e|--edit] [--just-commit] [--full] [-v|--verbose] [-C <change-number>] \\\n[-F <filename>] [--ptr|--p4 <p4-path-and/or-revision>] [--p4-desc]\n\nPerforce client state importer. Creates a git commit on the current\nbranch from a state the given p4 client and working directory hold.\n\n<specification> must be given and is expected to be a file which will be\nstored on the side branch under the name \"spec\".\nRemote-to-local mapping and the revisions of files are stored in \"have\",\nand the client definition - in \"client\".\n\n--client client Specify client name (saved in .git/p4/client for the next time)\n--full          Perform full import, don't even try to figure out what changed\n-y|--yes        Commit automatically (by default only index updated)\n--just-commit   To be used after you forgot to run with --yes first time\n-n|--dry-run    Do not update the index and do not commit\n-e|--edit       Edit commit description before committing\n-v|--verbose    Be more verbose. Can be given many times, increases verbosity\n-F file         Take description for the commit from a file in the\n                next parameter\n-C change       Take description for the commit from this p4 change\n--p4|--ptr p4-path-and/or-revision Take description for the commit from the p4\n                change described by this p4 path, possibly including revision\n                specification\n--p4-desc       Increase amount of junk from p4 change description\n\nThe descriptions taken from p4 changes given by -C and --p4 will\nbe concatenated if the options given multiple times.\n\nEOF\n\t    exit(0);\n\t}\n\twarn \"$0: spec was already set, $SPEC ignored\\n\" if defined($SPEC);\n\t$SPEC = $f;\n    }\n}\nread_args(@ARGV);\n\nlocal ($GIT_DIR) = qx{git rev-parse --git-dir};\n$GIT_DIR =~ s/\\r?\\n$//s if defined($GIT_DIR);\ndie \"$0: git directory not found\\n\" if !defined($GIT_DIR) or !-d $GIT_DIR;\n\nlocal $editor = $ENV{VISUAL};\n$editor = $ENV{EDITOR} unless defined($editor);\n$editor = 'd:/Programs/Vim/vim70/gvim.exe' unless defined($editor);\ndie \"$0: no editor defined\\n\" unless defined($editor);\n\n# P4 client was given in command-line. Store it\nif ( defined($P4CLIENT) ) {\n    mkdir \"$GIT_DIR/p4\", 0777;\n    if ( open(F, '>', \"$GIT_DIR/p4/client\") ) {\n\tprint F \"$P4CLIENT\\n\";\n\tclose(F);\n    } else {\n\tdie \"$0: cannot store client name: $!\\n\"\n    }\n} else {\n    if ( open(F, '<', \"$GIT_DIR/p4/client\") ) {\n\t($P4CLIENT) = <F>;\n\tclose(F);\n\t$P4CLIENT =~ s/^\\s*//,$P4CLIENT =~ s/\\s*$// if defined($P4CLIENT);\n    }\n}\ndie \"P4 client not defined\\n\" if !defined($P4CLIENT) or !length($P4CLIENT);\nprint \"reading P4 client $P4CLIENT\\n\" if $VERBOSE;\nlocal ($P4ROOT, $p4clnt, $P4HOST);\nopen(my $fdo, '>', \"$GIT_DIR/p4/client.def\") or die \"p4/client.def: $!\\n\";\nbinmode($fdo);\nopen(my $fdi, '-|', \"p4 client -o $P4CLIENT\") or die \"p4 client: $!\\n\";\nbinmode($fdi);\nmy $last_line_len = 0;\nwhile (<$fdi>) {\n    next if /^#/o;\n    if ( m/^\\s*Root:\\s*(\\S+)[\\\\\\/]*\\s*$/so ) { $P4ROOT = $1 }\n    elsif ( m/^\\s*Client:\\s*(\\S+)/o ) { $p4clnt = $1 }\n    elsif ( m/^\\s*Host:\\s*(\\S+)/o ) { $P4HOST = $1 }\n    ($VERBOSE and print), next if /^(Access|Update):/;\n    s/\\r?\\n$//so;\n    my $len = length($_);\n    print $fdo \"$_\\n\" if $len or $len != $last_line_len;\n    $last_line_len = $len;\n}\nclose($fdi);\nclose($fdo);\n\ndie \"Client root not defined\\n\" unless defined($P4ROOT);\nif ( $VERBOSE ) {\n    print \"GIT_DIR: $GIT_DIR\\n\";\n    print \"Root: $P4ROOT (cwd: $start_dir)\\n\";\n    print \"Host: $P4HOST\\n\";\n    print \"Client: $p4clnt\\n\" if $p4clnt ne $P4CLIENT;\n}\nmy ($git_head,$git_p4_head,$git_p4_have) = &git_p4_init;\n\nif ($JUST_COMMIT) {\n    git_p4_commit($git_head, $git_p4_head);\n    exit 0;\n}\n\nlocal %gitignore_dirs = ();\n$gitignore_dirs{'/'} = read_filter_file(\"$GIT_DIR/info/exclude\");\npush(@{$gitignore_dirs{'/'}}, @{read_filter_file('.gitignore')});\n\nmy %git_index = ();\n$/ = \"\\0\";\nmy @git_X = ();\nprint \"Reading git file list(git ls-files @git_X --cached -z)...\\n\" if $VERBOSE;\nforeach ( qx{git ls-files @git_X --cached -z} ) {\n    chop; # chop \\0\n    next if m/^\\.gitignore$/o;\n    next if m/\\/\\.gitignore$/o;\n    next if filtered($_);\n    $git_index{$_} = 1;\n}\n\nmy @git_add = ();\nmy @git_addx = ();\nmy @git_del = ();\nmy @git_upd = ();\n\nprint \"Reading P4 file list...\\n\" if $VERBOSE;\nlocal ($Conflicts,$Ignored,$Added,$Deleted,$Updated) = (0,0,0,0,0);\n$/ = \"\\n\";\nmy $in_name = 0;\nmy @root = split(/[\\/\\\\]+/, $P4ROOT);\nmy %p4_index = ();\nmy %p4_a_lc = ();\nmy %lnames = ();\nmy %lconflicts = ();\nif (opendir(DIR, '.')) {\n    $lnames{'.'} = [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n    closedir(DIR);\n    #print \"read $start_dir (\",scalar(@{$lnames{'.'}}),\")\\n\";\n}\nopen(my $have, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have |\") or\n    die \"$0: failed to start p4: $!\\n\";\nbinmode($have);\n$P4HAVE_FILE = \"$GIT_DIR/p4/have\";\nopen(my $storedhave, '>', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\nbinmode($storedhave);\nmy $ent;\nwhile (defined($ent=read_pydict_entry($have))) {\n    next if !defined($ent->{depotFile}) or !defined($ent->{clientFile});\n    my $a = $ent->{depotFile};\n    $ent->{clientFile} =~ m!^//[^/]+/(.*)!o;\n    my $b = $1;\n    my @bb = split(/\\/+/, $b);\n    print $storedhave \"$a\\0$ent->{clientFile}\\0$ent->{haveRev}\\0\\n\";\n\n    if ( $^O eq 'MSWin32' ) {\n\t# stupid windows, daft activestate, dumb P4\n\t# This piece below is checking for file name conflicts\n\t# which happen on windows because of it mangling the names.\n\tmy $blc = lc $b;\n\tif ( $#bb > 0 ) {\n\t    my $path = '.';\n\t    foreach my $n (@bb[0 .. $#bb -1]) {\n\t\tmy @conflicts =\n\t\t    grep {lc $_ eq lc $n and $_ ne $n} @{$lnames{$path}};\n\t\tif (@conflicts and !exists($lconflicts{\"$path/$n\"})) {\n\t\t    warn \"warning: $a -> $b\\n\".\n\t\t\t \"warning: conflict between path \\\"$path/$n\\\" and \".\n\t\t\t \"local filesystem in \\\"@conflicts\\\"\\n\";\n\t\t    $Conflicts++;\n\t\t    $lconflicts{\"$path/$n\"} = 1;\n\t\t}\n\t\t$path .= \"/$n\";\n\t\tif (!exists($lnames{$path})) {\n\t\t    if (opendir(DIR, $path)) {\n\t\t\t$lnames{$path} =\n\t\t\t    [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n\t\t\tclosedir(DIR);\n\t\t\t#print \"read $path (\",scalar(@{$lnames{$path}}),\")\\n\";\n\t\t    }\n\t\t}\n\t    }\n\t}\n\tif (!exists($p4_a_lc{$blc})) {\n\t    $p4_a_lc{$blc} = [$a, $b];\n\t} else {\n\t    warn(\"warning: $a -> $b\\n\".\n\t\t \"warning: conflicts with \".\n\t\t $p4_a_lc{$blc}->[0].\" -> \".\n\t\t $p4_a_lc{$blc}->[1].\"\\n\");\n\t    $Conflicts++;\n\t    next;\n\t}\n    }\n\n    my $i;\n    for ($i = 0; $i < $#bb; ++$i) {\n\tmy $bdir = join('/',@bb[0 .. $i]) . '/';\n\tif ( !exists($gitignore_dirs{$bdir}) ) {\n\t    $gitignore_dirs{$bdir} = read_filter_file(\"$bdir.gitignore\");\n\t}\n    }\n    if (filtered($b)) {\n\tprint \" i $b\\n\" if $VERBOSE > 3;\n\t$Ignored++;\n\tnext\n    }\n    $p4_index{$b} = $a;\n    if ( exists($git_index{$b}) ) {\n\tmy $needup = 1;\n\tif (defined($git_p4_have)) {\n\t    $prev = $git_p4_have->{$a};\n\t    if (defined($prev)) {\n\t\t$prev->[0] =~ m!^//[^/]+/(.*)!o;\n\t\t$needup = 0 if ($b eq $1) and ($prev->[1] eq $ent->{haveRev});\n\t\tif ($needup and $VERBOSE > 1) {\n\t\t    my $reason;\n\t\t    $reason = 'local file' if $b ne $1;\n\t\t    $reason = 'revision' if $prev->[1] ne $ent->{haveRev};\n\t\t    print \"$a ($reason changed)\\n\";\n\t\t}\n\t    }\n\t}\n\tif ($needup) {\n\t    $Updated++;\n\t    push(@git_upd, $b);\n\t}\n    } else {\n\t$Added++;\n\tif ( $b =~ m/\\.(bat|cmd|pl|sh|exe|dll)$/io )\n\t{ push(@git_addx, $b) } else { push(@git_add, $b) }\n    }\n}\nclose($storedhave);\nclose($have);\nundef %p4_a_lc;\n\n@git_del = grep { !exists($p4_index{$_}) } keys %git_index;\n$Deleted = $#git_del + 1;\n\n#foreach (keys %git_index)\n#{ push(@git_del, $_) if !exists($p4_index{$_}) }\n\nif ( $DRYRUN ) {\n    print($#git_add+$#git_addx+ 2,\" files to add\\n\") if $VERBOSE;\n    print map {\" a $_\\n\"} @git_add if $VERBOSE > 2;\n    print map {\" a $_\\n\"} @git_addx if $VERBOSE > 2;\n    print($#git_del+1,\" files to unreg\\n\") if $VERBOSE;\n    print map {\" d $_\\n\"} @git_del if $VERBOSE > 2;\n    print($#git_upd+1,\" files to update\\n\") if $VERBOSE;\n    print map {\" u $_\\n\"} @git_upd if $VERBOSE > 2;\n    print \"added: $Added, unregd: $Deleted, updated: $Updated, ignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n} else {\n    if (@git_add || @git_addx) {\n\tprint($#git_add+$#git_addx+ 2,\n\t      \" files | git update-index --add -z --stdin\\n\")\n\t    if $VERBOSE;\n\tif (@git_add) {\n\t    open(GIT, '| git update-index --add --chmod=-x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_add;\n\t    close(GIT);\n\t}\n\tif (@git_addx) {\n\t    open(GIT, '| git update-index --add --chmod=+x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_addx;\n\t    close(GIT);\n\t}\n    }\n\n    if (@git_del) {\n\tprint($#git_del+1,\" files | git update-index --remove -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index --force-remove -z --stdin') or\n\t    die \"$0 git-update-index(del): $!\\n\";\n\tprint GIT map {print \" d $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_del;\n\tclose(GIT);\n    }\n\n    if (@git_upd) {\n\tprint($#git_upd+1,\" files | git update-index -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index -z --stdin') or\n\t    die \"$0 git-update-index(upd): $!\\n\";\n\tprint GIT map {print \" u $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_upd;\n\tclose(GIT);\n    }\n    \n    print \"added: $Added, unregd: $Deleted, updated: $Updated, ignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n    git_p4_commit($git_head, $git_p4_head) if $AUTO_COMMIT;\n}\n\nexit 0;\n\nsub filtered {\n    my $name = shift;\n    study($name);\n    my @path = split(/\\/+/o, $name);\n    my $dir = '';\n    $name = '';\n    \n    foreach my $d (@path) {\n\t$name .= $d;\n#\tprint STDERR \"$dir: $name $d\\n\" if $v;\n\tforeach my $re (@{$gitignore_dirs{'/'}}) {\n\t    return 1 if $name =~ m/$re/;\n\t    return 1 if $d =~ m/$re/;\n\t}\n\tif ( length($dir) and exists($gitignore_dirs{$dir}) ) {\n\t    foreach my $re (@{$gitignore_dirs{$dir}}) {\n\t\treturn 1 if $name =~ m/$re/;\n\t\treturn 1 if $d =~ m/$re/;\n\t    }\n\t}\n\t$name .= '/';\n\t$dir = $name;\n    }\n#    print STDERR \"$name not filtered\\n\" if $v;\n    return 0;\n}\n\nsub read_filter_file {\n    my @filts = ();\n    my $file = shift;\n    if ( open(my $if, '<', $file) ) {\n\tprint \"added ignore file $file\\n\" if $VERBOSE;\n\t$/ = \"\\n\";\n\twhile (my $l = <$if>) {\n\t    next if $l =~ /^\\s*#/o;\n\t    next if $l =~ /^\\s*$/o;\n\t    $l =~ s/[\\r\\n]+$//so;\n\t    $l =~ s/\\./\\\\./go;\n\t    $l =~ s/\\*/.*/go;\n\t    if ( $l =~ m/\\// ) {\n\t\t$l = \"^$l($|/)\";\n\t    } else {\n\t\t$l = \"(^|/)$l\\$\";\n\t    }\n\t    print \" filter $l\\n\" if $VERBOSE > 1;\n\t    push(@filts, qr/$l/);\n\t}\n\tclose($if);\n    }\n    return \\@filts;\n}\n\nsub r_pystr\n{\n    my $fd = shift;\n    my ($len,$str)=('','');\n    my ($c,$rd,$b) = (4,0,'');\n    while ($c > 0) {\n\t$rd = sysread($fd,$b,$c);\n\twarn(\"failed to read len: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data for len\"), return undef if !$rd;\n\t$len .= $b;\n\t$c -= $rd;\n    }\n    $len = unpack('V',$len);\n    while ($len > 0) {\n\t$rd = sysread($fd,$b,$len);\n\twarn(\"failed to read data: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data\"), return undef if !$rd;\n\t$str .= $b;\n\t$len -= $rd;\n    }\n    return $str;\n}\n\nsub read_pydict_entry\n{\n    my $f = shift;\n    my ($buf,$rd);\n    FIL: while (1) {\n\t# object type identifier\n\t$rd = sysread($f, $buf, 1);\n\tlast FIL if $rd == 0;\n\twarn(\"object type: $!\\n\"),last if $rd != 1;\n\t# '{' is a python marshalled dict\n\twarn(\"object type: not {\\n\"),last if $buf ne '{';\n\tmy $ent = {};\n\tPAIR: while (1) {\n\t    my ($b,$key);\n\t    # key type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"key type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$key = r_pystr($f);\n\t\twarn(\"key: $!\\n\"),last FIL if !defined($b);\n\t    } elsif ($b eq '0') { # NULL-element, end of entry\n\t\tlast PAIR;\n\t    } else {\n\t\twarn(\"key type: not s\");\n\t\tlast FIL;\n\t    }\n\t    # value type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"$key value type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$b = r_pystr($f);\n\t\twarn(\"$key value: $!\"),last FIL if !defined($b);\n\t\t$ent->{$key} = $b;\n\t    } else {\n\t\twarn(\"$key value type: not s ($b)\");\n\t\tlast FIL;\n\t    }\n\t}\n\treturn $ent;\n    }\n    return undef;\n}\n\nsub cl2msg {\n    my $cl = shift;\n    my($o1,$o2,$i);\n    if(!open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\twarn \"p4/msg: $!\\n\";\n\treturn;\n    }\n    binmode($o1);\n    if(!open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\twarn \"p4/p4msg: $!\\n\";\n\tclose($o1);\n\treturn\n    }\n    binmode($o2);\n    if(!open($i, '-|', \"p4 describe -s $cl\")){\n\twarn \"p4 describe: $!\\n\";\n\tclose($o1);\n\tclose($o2);\n\treturn\n    }\n    binmode($i);\n    print $o1 \"$cl: \" if $FULL_DESC;\n    print $o2 \"$cl: \";\n    my @a;\n    my $u = undef;\n    while (my $l = <$i>) {\n\tif ($l =~ /^Change \\d+ by (\\S+)@[^ ]* on ([^\\r\\n]*)/so) {\n\t    $u = $1;\n\t    $ENV{GIT_AUTHOR_DATE} = $2 if length($2);\n\t}\n\tlast if $FULL_DESC < 2 and $l =~ /^\\s*Affected files \\.{3}\\s*$/so;\n\t$l =~ s/\\r?\\n$//so;\n\tpush @a, $l;\n    }\n    close($i);\n    print $o2 substr($a[2],1),\"\\n\"; # p4 side-branch commit description\n    close($o2);\n    # import branch commit description\n    if ($FULL_DESC > 1) {\n\t# desc level 2+: keep the Change line\n\tprint $o1 map {\"$_\\n\"} (substr($a[2],1),\"\\n\",@a);\n    } else {\n\t# levels 0 and 1: remove the Change line\n\tprint $o1 map { (length($_) ? substr($_,1):'').\"\\n\" } @a[2..$#a];\n    }\n    close($o1);\n    if (defined($u)) {\n\tif (!exists($P4USERS{$u})) {\n\t    my ($mail,$name) = grep {/^(Email|FullName):/} qx{p4 user -o $u};\n\t    if ($? == 0 and defined($mail) and defined($name)) {\n\t\ts/^\\S+:\t([^\\r\\n]*)\\r?\\n$/$1/so for ($mail,$name);\n\t\tif (length($name) and length($mail)) {\n\t\t    $P4USERS{$u} = {name=>$name, email=>$mail};\n\t\t}\n\t    }\n\t}\n\tif ($P4USERS{$u}) {\n\t    $p4u = $P4USERS{$u};\n\t    $ENV{GIT_AUTHOR_NAME}  = $p4u->{name};\n\t    $ENV{GIT_AUTHOR_EMAIL} = $p4u->{email};\n\t}\n    }\n}\n\nsub git_p4_init {\n    my ($commit,$parent,$p4commit,$p4parent);\n    my ($HEAD) = qx{git rev-parse HEAD};\n    $HEAD = '' if $?;\n    my ($p4head) = qx{git rev-parse refs/p4import/$P4CLIENT};\n    $p4head = '' if $?;\n    s/\\r?\\n//gs for ($HEAD, $p4head);\n    die \"No HEAD commit! Refusing to import.\\n\" if !length($HEAD);\n    if (length($p4head)) {\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\t$p4parent = '' if !defined($p4parent);\n    } else {\n\t$commit = $p4parent = '';\n    }\n    while (($commit ne $HEAD) and length($p4parent)) {\n\t$p4head = $p4parent;\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\tif ($VERBOSE and ($HEAD eq $commit)) {\n\t    print \"found p4 import commit \";\n\t    system('git','name-rev',$p4head);\n\t}\n    }\n    warn \"Current HEAD was not imported from $P4CLIENT, doing full import\\n\"\n\tif $HEAD ne $commit;\n    my $p4have = undef;\n    if (!$FULL_IMPORT and ($HEAD eq $commit) and length($p4head)) {\n\tif (open(my $f, '-|', \"git cat-file blob $p4head:have\")) {\n\t    my $old = $/;\n\t    $/ = \"\\0\";\n\t    my $cnt = 0;\n\t    while(1) {\n\t\tmy $p4name = <$f>;\n\t\tlast if !defined($p4name);\n\t\t$p4name =~ s/^.//so if $cnt; # remove \\n\n\t\tmy $name = <$f>;\n\t\tmy $rev = <$f>;\n\t\tlast if !defined($name) or !defined($rev);\n\t\tchop($p4name,$name,$rev);\n\t\t++$cnt;\n\t\tif (defined($p4have)) {\n\t\t    $p4have->{$p4name} = [$name,$rev];\n\t\t} else {\n\t\t    $p4have = {$p4name=>[$name,$rev]};\n\t\t}\n\t    }\n\t    $/ = $old;\n\t    close($f);\n\t    print \"loaded $cnt revisions from $p4head\\n\" if $VERBOSE;\n\t}\n    }\n    return ($HEAD, $p4head, $p4have);\n}\n\nsub git_p4_commit {\n    my ($HEAD, $p4head) = @_;\n    my ($commit,$parent,$p4commit,$p4parent);\n\n    my ($fdo,$fdi,$rc);\n    $rc = system('git','diff-index','--exit-code','--quiet','--cached','HEAD');\n    if ($rc == 0) {\n\twarn(\"No changes\\n\");\n\treturn;\n    }\n\n    return if $DRYRUN;\n\n    my $p4x = \"$GIT_DIR/p4/idx.tmp\";\n    unlink($p4x);\n    my $oldidx = $ENV{GIT_INDEX_FILE};\n\n    $ENV{PAGER} = 'cat';\n    $ENV{GIT_INDEX_FILE} = $p4x;\n\n    if (!defined($SPEC) or !open(STDIN, '<', $SPEC)) {\n\tif ( $^O eq 'MSWin32' ) {\n\t    open(STDIN, '<', 'NUL') or die \"$SPEC: $!\\n\";\n\t} else {\n\t    open(STDIN, '<', '/dev/null') or die \"$SPEC: $!\\n\";\n\t}\n    }\n    my ($p4spec) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to store $SPEC in git repo\\n\" if $?;\n\n    open(STDIN, '<', \"$GIT_DIR/p4/client.def\") or die \"cldef: $!\\n\";\n    my ($p4clnt) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save mappings of $P4CLIENT in git repo\" if $?;\n\n    if (!defined($P4HAVE_FILE)) {\n\tprint \"reading state of $P4CLIENT\\n\" if $VERBOSE;\n\t$P4HAVE_FILE = \"$GIT_DIR/p4/have\"; \n\topen($fdo, '>', $P4HAVE_FILE) or die \"p4/have: $!\\n\";\n\tbinmode($fdo);\n\topen($fdi, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have|\") or\n\t    die \"p4 have: $!\\n\";\n\tbinmode($fdi);\n\tmy $ent;\n\twhile (defined($ent=read_pydict_entry($fdi))) {\n\t    next if !defined($ent->{depotFile});\n\t    next if !defined($ent->{clientFile});\n\t    print $fdo \"$ent->{depotFile}\\0\",\n\t\t       \"$ent->{clientFile}\\0\",\n\t\t       \"$ent->{haveRev}\\0\\n\";\n\t}\n\tclose($fdi);\n\tclose($fdo);\n    }\n\n    open(STDIN, '<', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\n    my ($p4have) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save state of $P4CLIENT in git repo\" if $?;\n\n    unlink(\"$GIT_DIR/p4/msg\", \"$GIT_DIR/p4/p4msg\");\n    foreach my $i (@DESC) {\n\t$i =~ s/^(.)//o;\n\tif ('c' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    cl2msg($i);\n\t} elsif ('f' eq $1) {\n\t    my($o1,$o2,$i);\n\t    if (open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\t\tif (open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\t\t    if (open($i, '<', $i)) {\n\t\t\tmy $n = 0;\n\t\t\twhile(<$i>) {\n\t\t\t    $n++;\n\t\t\t    print $o1 $_;\n\t\t\t    print $o2 $_ if $n == 1;\n\t\t\t}\n\t\t\tclose($i);\n\t\t    }\n\t\t    close($o2);\n\t\t}\n\t\tclose($o1);\n\t    }\n\t} elsif ('4' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    my ($change)=qx{p4 changes -m1 $i};\n\t    if (!defined($change) or $change !~ m/\\s+(\\d+)\\s/) {\n\t\tdie \"$i does not resolve into a change number\\n\";\n\t    }\n\t    cl2msg($1);\n\t}\n    }\n    system(\"$editor $GIT_DIR/p4/msg\") if $EDIT_COMMIT;\n\n    if (defined($oldidx)) { $ENV{GIT_INDEX_FILE} = $oldidx }\n    else { delete $ENV{GIT_INDEX_FILE} }\n\n    #\n    # Store the imported file data\n    #\n\n    if ( $^O eq 'MSWin32' ) { open(STDERR, \"NUL\") }\n    else { open(STDERR, \"/dev/null\") }\n\n    my ($tree) = qx{git write-tree};\n    die \"Failed to write current tree\\n\" if $?;\n    $parent = length($HEAD) ? \"-p $HEAD\": '';\n    open(STDIN, '<', \"$GIT_DIR/p4/msg\") or die \"p4/msg: $!\\n\";\n    $tree =~ s/\\r?\\n//gs;\n    ($commit)=qx{git commit-tree $tree $parent};\n    die \"failed to commit current tree\\n\" if $?;\n    s/\\r?\\n//gs for ($commit);\n\n    #\n    # Storing import control data\n    #\n    $ENV{GIT_INDEX_FILE} = $p4x;\n    open($fdo, '|-', 'git update-index --add --index-info') or\n\tdie \"could not start git update-index\\n\";\n    binmode($fdo);\n    s/\\r?\\n//gs for ($p4spec,$p4clnt,$p4have);\n    print $fdo \"100644 $p4spec\\tspec\\n\";\n    print $fdo \"100644 $p4clnt\\tclient\\n\";\n    print $fdo \"100644 $p4have\\thave\\n\";\n    close($fdo);\n    if($?) {\n\tdie \"Failed to store $SPEC in p4import index and git repo\\n\".\n\t    \"Failed to save mappings of $P4CLIENT in p4import index and git repo\\n\".\n\t    \"Failed to save state of $P4CLIENT in p4import index and git repo\\n\"\n    }\n    my ($p4tree)=qx{git write-tree};\n    die \"Failed to store $SPEC (tree) in git repo\\n\" if $?;\n\n    # Bind import control data to the file data\n    $p4parent=\"-p $commit\";\n    $p4parent=\"$p4parent -p $p4head\" if length($p4head);\n    open(STDIN, '<', \"$GIT_DIR/p4/p4msg\") or die \"p4/p4msg: $!\\n\";\n    $p4tree =~ s/\\r?\\n//gs;\n    ($p4commit)=qx{git commit-tree $p4tree $p4parent};\n    die \"Failed to store $SPEC (commit) in git repo\\n\" if $?;\n    $p4commit =~ s/\\r?\\n//gs;\n\n    # Finishing touches: update references\n    system('git','update-ref','-m','backup ref of current branch',\n\t   'p4/backup-HEAD','HEAD');\n    system('git','update-ref','-m','backup ref of p4import',\n\t   'p4/backup-p4import',\"refs/p4import/$P4CLIENT\");\n    $rc = system('git','update-ref','-m','data of p4import','HEAD',$commit);\n    die \"Failed to update HEAD\\n\" if $rc;\n    $rc = system('git','update-ref','-m','p4import',\"refs/p4import/$P4CLIENT\",$p4commit);\n    die \"Failed to store $SPEC (reference) in git repo\\n\" if $rc;\n\n    if ($VERBOSE) {\n\tprint STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev refs/p4import/$P4CLIENT}), \":\\n\";\n\tsystem('git','log','--max-count=1','--pretty=format:%h %s%n',$p4commit);\n    }\n    print STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev HEAD}), \":\\n\";\n    system('git','log','--max-count=1','--pretty=format:%h %s%n',$commit);\n}\n\n"},{"id":"42991","messageId":"20070522211334.GC30871@steel.home","threadId":"7990","inReplyTo":"81b0412b0705100637g1072f5c7y54f781b67a569690@mail.gmail.com","subject":"Yet another Perforce importer","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2007-05-22T21:13:34Z","receivedAt":"2007-05-22T21:13:34Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"- primitive export support with --p4-edit-changed[=branch]\n\n  If no branch given the HEAD is assumed; this is useful when HEAD is\n  updated, but index and working directory are not. This is the case\n  after a remote repo does a push into a p4 mirroring repo. This has\n  its problems (only one commit will be described in p4 changelist),\n  but it has its uses too (default clone creates a push configuration\n  which modifies the master of the source repo), so it was left in.\n  The working directory of the p4 mirroring repo must be kept clean\n  (no changes not registered in the index. It's somewhat problematic\n  on cygwin - it keeps changing filemodes). Also, p4 cannot have\n  non-linear history (they wouldn't admit this, but they don't have\n  non-linear history, branching, merging and they are very slow), so\n  the branch to be exported is expected to fast-forward from the\n  mirroring branch (this probably can be changed, but it would be nice\n  to know in advance which files will be changed after the merge: to\n  checkout them from p4).\n\n  The workflow is like this:\n\n    $ git clone p4-mirror working\n    $ cd working; edit ...; git commit ...\n    $ git push origin +master:refs/remotes/working/master\n    $ cd ../p4-mirror\n    $ git-p4-import --p4-edit-changed=working/master\n    The state of working/master(123847298742...) is checked out\n    A p4 changelist 1234567 is prepared\n    $ p4 submit 1234567\n    Changelist 1234567 renamed 8375983 and submitted\n    $ git-p4-import -y -C 8375983\n\n  The --p4-edit-changed call will checkout the changed p4 files for\n  edit, addition and deletion, prepare submission by populating it\n  with commit descriptions, und update the files with the state from\n  the working branch. Addition of new files can be very problematic:\n  client view must be configured so that p4 knows where to places them\n  on server.\n  Submission left to the user: with that piece of sh%t the p4 is most\n  of us probably have to follow some special procedure for submission\n  anyway (I have to).\n  If someone else changes p4 repo and you import the changes, don't\n  forget to rebase your working branches: the current code blocks\n  export if there are changes between the working branch and mirroring\n  branch. This is the line where rev-list is called. Feel free to fix\n  that. I didn't because I just had enough of p4 stupidity for that day.\n\n- Add --diffs to show changes between filesystem, index and HEAD\n  Useful after a \"blind push\" (the one which happens by default) on\n  the mirror branch: to see where was changed.\n\n- Allow running the script without any description. Either a\n  description or --edit must be given: for mirroring branch commit.\n  A \"-m\" for message like in git commit should be a good idea,\n  perhaps: most descriptions of p4 submissions are useless and the\n  \"spaghetti\" of a p4 checkout seldom can be defined with one\n  changelist.\n\n- Diverse small and large fixes, for instance a problem with creation\n  of empty commits which removed all files was fixed\n\nIt is still untested on Linux.\n\n@rem = 'NT: CMD.EXE vim: syntax=perl noet sw=4\n@perl -x -s %0 -- %*\n@exit\n@rem ';\n#!perl -w\n#line 7\n\nlocal $VERBOSE = 0;\nlocal $DRYRUN = 0;\nlocal $SHOW_DIFFS = 0;\nlocal $AUTO_COMMIT = 0;\nlocal $JUST_COMMIT = 0;\nlocal $P4CLIENT = undef;\nlocal @EDIT_COMMIT = 0;\nlocal @FULL_IMPORT = 0;\nlocal @DESC = ();\nlocal $SPEC = undef;\nlocal @P4ARGS = ();\nlocal $P4HAVE_FILE = undef;\nlocal %P4USERS = ();\nlocal $FULL_DESC = 1;\nlocal $HEAD_FROM_P4 = 0;\nlocal $P4_EDIT_CHANGED = 0;\nlocal $P4_EDIT_HEAD = undef;\n\npush(@P4ARGS, '-P', $ENV{P4PASSWD})\n    if defined($ENV{P4PASSWD}) and length($ENV{P4PASSWD});\nuse Cwd;\nlocal $start_dir = cwd();\n\nsub read_args {\n    my ($in_client, $in_cl, $in_fi, $in_p4, $no_opt) = (0,0,0,0,0);\n    foreach my $f ( @_ ) {\n\tgoto _files if $no_opt;\n\tif ($in_client) { $in_client = 0; $P4CLIENT = $f; next }\n\tif ($in_cl) { $in_cl=0; push(@DESC,\"c$f\"); next }\n\tif ($in_fi) { $in_fi=0; push(@DESC,\"f$f\"); next }\n\tif ($in_p4) { $in_p4=0; push(@DESC,\"4$f\"); next }\n\t$no_opt=1, next if $f eq '--';\n\t$DRYRUN=1, next if $f eq '-n' or $f eq '--dry-run';\n\t$SHOW_DIFFS=$DRYRUN=1, next if $f eq '--diffs';\n\t$AUTO_COMMIT=1, next if $f eq '-y' or $f eq '--yes';\n\t$JUST_COMMIT=1, next if $f eq '--just-commit';\n\t$EDIT_COMMIT=1, next if ($f eq '-e') or ($f eq '--edit');\n\t$FULL_IMPORT=1, next if $f eq '--full';\n\t$FULL_DESC++, next if $f eq '--p4-desc';\n\t$P4_EDIT_CHANGED=1, next if $f eq '--p4-edit-changed';\n\tif ($f =~ /^--p4-edit-changed=(.*)/) {\n\t    $P4_EDIT_CHANGED=1;\n\t    $P4_EDIT_HEAD=$1;\n\t    next \n\t}\n\t$VERBOSE++, next if $f eq '-v' or $f eq '--verbose';\n\t$in_client = 1, next if $f eq '--client';\n\t$P4CLIENT = $1, next if $f =~ /^--client=(.*)/;\n\t$in_cl = 1, next if $f eq '-C';\n\tpush(@DESC,\"c$1\"), next if $f =~ /^--changelist=(.*)/;\n\t$in_fi = 1, next if $f eq '-F';\n\tpush(@DESC,\"f$1\"), next if $f =~ /^--file=(.*)/;\n\t$in_p4 = 1, next if ($f eq '--ptr') or ($f eq '--p4');\n\tpush(@DESC,\"4$2\"), next if $f =~ /^--(p4|ptr)=(.*)/;\n\n\tif ($f eq '--help' or $f eq '-h') {\n\t    print <<EOF;\n$0 [-n|--dry-run] [-y|--yes] [--client <client-name>] [--diffs] \\\n[-e|--edit] [--just-commit] [--full] [-v|--verbose] [-C <change-number>] \\\n[-F <filename>] [--ptr|--p4 <p4-path-and/or-revision>] [--p4-desc] \\\n[--] [<specification>] \n\nPerforce client state importer. Creates a git commit on the current\nbranch from a state the given p4 client and working directory hold.\n\n<specification> must be given and is expected to be a file which will be\nstored on the side branch under the name \"spec\".\nRemote-to-local mapping and the revisions of files are stored in \"have\",\nand the client definition - in \"client\".\n\n--client client Specify client name (saved in .git/p4/client for the next time)\n--full          Perform full import, don't even try to figure out what changed\n-y|--yes        Commit automatically (by default only index updated)\n--just-commit   To be used after you forgot to run with --yes first time\n-n|--dry-run    Do not update the index and do not commit\n-e|--edit       Edit commit description before committing\n-v|--verbose    Be more verbose. Can be given many times, increases verbosity\n\n--file=file\n-F file         Take description for the commit from a file in the\n                next parameter\n\n--changelist=change\n-C change       Take description for the commit from this p4 change\n\n--p4|--ptr p4-path-and/or-revision\n--p4|--ptr=p4-path-and/or-revision\n                Take description for the commit from the p4 change described\n                by this p4 path, possibly including revision specification\n\n--p4-desc       Increase amount of junk from p4 change description\n\n--diffs         Show files which are different between local filesystem, index,\n                and the current HEAD. Does not do anything else\n\n--p4-edit-changed\n--p4-edit-changed=sha1\n                Merge with the given sha1 and prepare a p4 submission.\n                Current HEAD must fast-forward to sha1. If sha1 is omited,\n                the current HEAD is assumed. The working directory must\n                have no local changes.\n\nThe descriptions taken from p4 changes given by -C and --p4 will\nbe concatenated if the options given multiple times.\n\"--\" can be used to separate options from description files.\n\nEOF\n\t    exit(0);\n\t}\n\tdie \"$0: unknown option $f\\n\" if $f =~ /^-/;\n    _files:\n\twarn \"$0: spec was already set, $SPEC ignored\\n\" if defined($SPEC);\n\t$SPEC = $f;\n    }\n}\nread_args(@ARGV);\n\nlocal ($GIT_DIR) = qx{git rev-parse --git-dir};\n$GIT_DIR =~ s/\\r?\\n$//s if defined($GIT_DIR);\ndie \"$0: git directory not found\\n\" if !defined($GIT_DIR) or !-d $GIT_DIR;\n\nlocal $editor = $ENV{VISUAL};\n$editor = $ENV{EDITOR} unless defined($editor);\n$editor = 'd:/Programs/Vim/vim70/gvim.exe' unless defined($editor);\ndie \"$0: no editor defined\\n\" unless defined($editor);\n\nif ($SHOW_DIFFS) {\n    my $sep = $/;\n    $/=\"\\0\";\n    my ($show, $cnt) = (0, 0);\n    if (open(F, '-|', 'git diff-files -r --name-only -z')) {\n\twhile (<F>) {\n\t    my $c = chop;\n\t    $_ .= $c if $c ne \"\\0\";\n\t    print \"Changed files:\\n\" if !$show;\n\t    print \" $_\\n\";\n\t    $show = 1;\n\t    $cnt++;\n\t}\n\tclose(F);\n    }\n    if (open(F, '-|', 'git diff-index --cached -r -z HEAD')) {\n\t$show = 0;\n\tmy ($diff, $info) = (0, 1);\n\twhile (<F>) {\n\t    my $c = chop;\n\t    $_ .= $c if $c ne \"\\0\";\n\t    if ($info) {\n\t\tnext if !/^:(\\d{6}) (\\d{6}) ([0-9a-f]{40}) ([0-9a-f]{40}) ./o;\n\t\t# show only content changes, p4 does not support exec-bit anyway\n\t\t$diff = $3 ne $4;\n\t    } elsif ($diff) {\n\t\tprint \"Changes between index and HEAD:\\n\" if !$show;\n\t\tprint \" $_\\n\";\n\t\t$show = 1;\n\t\t$cnt++;\n\t    }\n\t    $info = !$info;\n\t}\n\tclose(F);\n    }\n    $/ = $sep;\n    exit($cnt ? 1: 0);\n}\n\n# P4 client was given in command-line. Store it\nif ( defined($P4CLIENT) ) {\n    mkdir \"$GIT_DIR/p4\", 0777;\n    if ( open(F, '>', \"$GIT_DIR/p4/client\") ) {\n\tprint F \"$P4CLIENT\\n\";\n\tclose(F);\n    } else {\n\tdie \"$0: cannot store client name: $!\\n\"\n    }\n} else {\n    if ( open(F, '<', \"$GIT_DIR/p4/client\") ) {\n\t($P4CLIENT) = <F>;\n\tclose(F);\n\t$P4CLIENT =~ s/^\\s*//,$P4CLIENT =~ s/\\s*$// if defined($P4CLIENT);\n    }\n}\ndie \"P4 client not defined\\n\" if !defined($P4CLIENT) or !length($P4CLIENT);\nprint \"reading P4 client $P4CLIENT\\n\" if $VERBOSE;\nlocal ($P4ROOT, $p4clnt, $P4HOST);\nopen(my $fdo, '>', \"$GIT_DIR/p4/client.def\") or die \"p4/client.def: $!\\n\";\nbinmode($fdo);\nopen(my $fdi, '-|', \"p4 client -o $P4CLIENT\") or die \"p4 client: $!\\n\";\nbinmode($fdi);\nmy $last_line_len = 0;\nwhile (<$fdi>) {\n    next if /^#/o;\n    if ( m/^\\s*Root:\\s*(\\S+)[\\\\\\/]*\\s*$/so ) { $P4ROOT = $1 }\n    elsif ( m/^\\s*Client:\\s*(\\S+)/o ) { $p4clnt = $1 }\n    elsif ( m/^\\s*Host:\\s*(\\S+)/o ) { $P4HOST = $1 }\n    ($VERBOSE and print), next if /^(Access|Update):/;\n    s/\\r?\\n$//so;\n    my $len = length($_);\n    print $fdo \"$_\\n\" if $len or $len != $last_line_len;\n    $last_line_len = $len;\n}\nclose($fdi);\nclose($fdo);\n\ndie \"Client root not defined\\n\" unless defined($P4ROOT);\nif ( $VERBOSE ) {\n    print \"GIT_DIR: $GIT_DIR\\n\";\n    print \"Root: $P4ROOT (cwd: $start_dir)\\n\";\n    print \"Host: $P4HOST\\n\";\n    print \"Client: $p4clnt\\n\" if $p4clnt ne $P4CLIENT;\n}\n\nif ($P4_EDIT_CHANGED) {\n    my $rc = system('git', 'diff-files', '--quiet');\n    exit(127) if $rc & 0xff; # error starting the program\n    die \"$0: there are changes in $P4ROOT. Import them first.\\n\" if $rc;\n    $P4_EDIT_HEAD = 'HEAD' if !defined($P4_EDIT_HEAD);\n    my ($mergehead) = qx{git rev-parse $P4_EDIT_HEAD};\n    exit(127) if $?;\n    exit(1) if !defined($mergehead);\n    $mergehead =~ s/\\r?\\n//gs;\n    exit(1) if !length($mergehead);\n    print \"Checking out $P4_EDIT_HEAD ($mergehead) for p4 edit\\n\" if $VERBOSE;\n    # Check if the give reference is a direct descendant of current branch\n    if ($P4_EDIT_HEAD ne 'HEAD') {\n\tmy ($sha1) = qx{git rev-list --max-count=1 $mergehead..HEAD};\n\texit(127) if $?;\n\tdie \"$0: HEAD does not fast-forward to $P4_EDIT_HEAD\\n\"\n\t    if defined($sha1) and $sha1 =~ /^[0-9a-f]{40}\\b/;\n    }\n    my $cnt;\n    my @files = ();\n    my $sep = $/;\n    $/=\"\\0\";\n    if (open(F, '-|', \"git diff-index -R --cached -r -z $mergehead\")) {\n\tmy ($diff, $info, $M) = (0, 1, '');\n\twhile (<F>) {\n\t    my $c = chop;\n\t    $_ .= $c if $c ne \"\\0\";\n\t    if ($info) {\n\t\tnext if !/^:\\d{6} \\d{6} ([0-9a-f]{40}) ([0-9a-f]{40}) (\\w+)/o;\n\t\t# use only content changes, p4 does not support exec-bit\n\t\t$diff = $1 ne $2;\n\t\t$M = $3; # change type marker\n\t    } elsif ($diff) {\n\t\tprint \"$M $_\\n\" if $VERBOSE;\n\t\tdie \"File contains characters which p4 cannot support\\n\"\n\t\t    if /[\\n@#%*]/s;\n\t\tpush @files, \"$M$_\";\n\t\t$cnt++;\n\t    }\n\t    $info = !$info;\n\t}\n\tclose(F);\n    }\n    $/ = $sep;\n    if (!$cnt) {\n\twarn \"$0: No content changes found between HEAD and $P4_EDIT_HEAD\";\n\texit(0);\n    }\n    # Create a new changelist\n    my $p4;\n    open($p4, \"p4 -c $P4CLIENT -H $P4HOST -d $P4ROOT change -o|\") or\n\tdie \"$0: failed to create changelist\\n\";\n    my @desc = map {s/\\r?\\n//so; $_} <$p4>;\n    close($p4);\n    open($p4, '>', \"$GIT_DIR/p4/changelist\") or\n\tdie \"$GIT_DIR/p4/changelist: $!\\n\";\n    foreach (@desc) {\n\tprint $p4 \"$_\\n\";\n\tif (/^Description:/o) {\n\t    my $range = \"..$mergehead\";\n\t    $range = \"${mergehead}^..$mergehead\" if $P4_EDIT_HEAD eq 'HEAD';\n\t    if (open(my $fd, '-|', \"git log $range\")) {\n\t\twhile(<$fd>) {\n\t\t    # I believe it is not possible to save this information\n\t\t    # in Perforce.\n\t\t    next if /^(commit |Author:|Date:)/;\n\t\t    s/\\r?\\n$//so;\n\t\t    next if !length($_);\n\t\t    s/^\\s+//o;\n\t\t    print $p4 \" $_\\n\";\n\t\t}\n\t\tclose($fd);\n\t    }\n\t}\n    }\n    close($p4);\n    open(STDIN, '<', \"$GIT_DIR/p4/changelist\") or\n\tdie \"$GIT_DIR/p4/changelist: $!\\n\";\n    open($p4, \"p4 -c $P4CLIENT -H $P4HOST -d $P4ROOT change -i|\") or\n\tdie \"$0: failed to create changelist\\n\";\n    my ($newchange) = grep {s/^Change (\\d+) created\\b.*/$1/so} <$p4>;\n    close($p4);\n    print \"Checking out P4 files in changelist $newchange\\n\" if $VERBOSE;\n    # open files for edit\n    $cnt = 0;\n    open($p4, '>', \"$GIT_DIR/p4/files\") or die \"$GIT_DIR/p4/files: $!\\n\";\n    print $p4 \"-c\\n$newchange\\n\";\n    print $p4 (map {++$cnt; substr($_,1).\"\\n\"} grep {/^M/} @files);\n    close($p4);\n    sub runp4 {\n\treturn system('p4','-c',$P4CLIENT,'-H',$P4HOST,'-d',$P4ROOT,@_);\n    }\n    runp4('-x',\"$GIT_DIR/p4/files\", 'edit') if $cnt;\n\n    $cnt = 0;\n    open($p4, '>', \"$GIT_DIR/p4/files\") or die \"$GIT_DIR/p4/files: $!\\n\";\n    print $p4 \"-c\\n$newchange\\n\";\n    print $p4 (map {++$cnt; substr($_,1).\"\\n\"} grep {/^A/} @files);\n    close($p4);\n    runp4('-x',\"$GIT_DIR/p4/files\", 'add') if $cnt;\n\n    $cnt = 0;\n    open($p4, '>', \"$GIT_DIR/p4/files\") or die \"$GIT_DIR/p4/files: $!\\n\";\n    print $p4 \"-c\\n$newchange\\n\";\n    print $p4 (map {++$cnt; substr($_,1).\"\\n\"} grep {/^D/} @files);\n    close($p4);\n    runp4('-x',\"$GIT_DIR/p4/files\", 'delete') if $cnt;\n\n    # p4 modifies working directory on checkout, stupid thing\n    system('git', 'update-index', '--refresh');\n    $rc = system('git', 'read-tree', '-m', '-u', $mergehead);\n    exit(127) if $rc & 0xff;\n    exit(1) if $rc;\n    print \"The state of $P4_EDIT_HEAD($mergehead) is checked out.\\n\";\n    print \"A p4 changelist $newchange is prepared.\\n\";\n    exit(0);\n}\n\nmy ($git_head,$git_p4_head,$git_p4_have) = &git_p4_init;\n\nif ($JUST_COMMIT) {\n    git_p4_commit($git_head, $git_p4_head);\n    exit 0;\n}\n\nlocal %gitignore_dirs = ();\n$gitignore_dirs{'/'} = read_filter_file(\"$GIT_DIR/info/exclude\");\npush(@{$gitignore_dirs{'/'}}, @{read_filter_file('.gitignore')});\n\nmy %git_index = ();\n$/ = \"\\0\";\nmy @git_X = ();\nprint \"Reading git file list(git ls-files @git_X --cached -z)...\\n\" if $VERBOSE;\nforeach ( qx{git ls-files @git_X --cached -z} ) {\n    chop; # chop \\0\n    next if m/^\\.gitignore$/o;\n    next if m/\\/\\.gitignore$/o;\n    next if filtered($_);\n    $git_index{$_} = 1;\n}\n\nmy @git_add = ();\nmy @git_addx = ();\nmy @git_del = ();\nmy @git_upd = ();\n\nprint \"Reading P4 file list...\\n\" if $VERBOSE;\nlocal ($Conflicts,$Ignored,$Added,$Deleted,$Updated) = (0,0,0,0,0);\n$/ = \"\\n\";\nmy $in_name = 0;\nmy @root = split(/[\\/\\\\]+/, $P4ROOT);\nmy %p4_index = ();\nmy %p4_a_lc = ();\nmy %lnames = ();\nmy %lconflicts = ();\nif (opendir(DIR, '.')) {\n    $lnames{'.'} = [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n    closedir(DIR);\n    #print \"read $start_dir (\",scalar(@{$lnames{'.'}}),\")\\n\";\n}\nopen(my $have, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have |\") or\n    die \"$0: failed to start p4: $!\\n\";\nbinmode($have);\n$P4HAVE_FILE = \"$GIT_DIR/p4/have\";\nopen(my $storedhave, '>', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\nbinmode($storedhave);\nmy $ent;\nwhile (defined($ent=read_pydict_entry($have))) {\n    next if !defined($ent->{depotFile}) or !defined($ent->{clientFile});\n    my $a = $ent->{depotFile};\n    $ent->{clientFile} =~ m!^//[^/]+/(.*)!o;\n    my $b = $1;\n    my @bb = split(/\\/+/, $b);\n    print $storedhave \"$a\\0$ent->{clientFile}\\0$ent->{haveRev}\\0\\n\";\n\n    if ( $^O eq 'MSWin32' ) {\n\t# stupid windows, daft activestate, dumb P4\n\t# This piece below is checking for file name conflicts\n\t# which happen on windows because of it mangling the names.\n\tmy $blc = lc $b;\n\tif ( $#bb > 0 ) {\n\t    my $path = '.';\n\t    foreach my $n (@bb[0 .. $#bb -1]) {\n\t\tmy @conflicts =\n\t\t    grep {lc $_ eq lc $n and $_ ne $n} @{$lnames{$path}};\n\t\tif (@conflicts and !exists($lconflicts{\"$path/$n\"})) {\n\t\t    warn \"warning: $a -> $b\\n\".\n\t\t\t \"warning: conflict between path \\\"$path/$n\\\" and \".\n\t\t\t \"local filesystem in \\\"@conflicts\\\"\\n\";\n\t\t    $Conflicts++;\n\t\t    $lconflicts{\"$path/$n\"} = 1;\n\t\t}\n\t\t$path .= \"/$n\";\n\t\tif (!exists($lnames{$path})) {\n\t\t    if (opendir(DIR, $path)) {\n\t\t\t$lnames{$path} =\n\t\t\t    [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n\t\t\tclosedir(DIR);\n\t\t\t#print \"read $path (\",scalar(@{$lnames{$path}}),\")\\n\";\n\t\t    }\n\t\t}\n\t    }\n\t}\n\tif (!exists($p4_a_lc{$blc})) {\n\t    $p4_a_lc{$blc} = [$a, $b];\n\t} else {\n\t    warn(\"warning: $a -> $b\\n\".\n\t\t \"warning: conflicts with \".\n\t\t $p4_a_lc{$blc}->[0].\" -> \".\n\t\t $p4_a_lc{$blc}->[1].\"\\n\");\n\t    $Conflicts++;\n\t    next;\n\t}\n    }\n\n    my $i;\n    for ($i = 0; $i < $#bb; ++$i) {\n\tmy $bdir = join('/',@bb[0 .. $i]) . '/';\n\tif ( !exists($gitignore_dirs{$bdir}) ) {\n\t    $gitignore_dirs{$bdir} = read_filter_file(\"$bdir.gitignore\");\n\t}\n    }\n    if (filtered($b)) {\n\tprint \" i $b\\n\" if $VERBOSE > 3;\n\t$Ignored++;\n\tnext\n    }\n    $p4_index{$b} = $a;\n    if ( exists($git_index{$b}) ) {\n\tmy $needup = 1;\n\tif (defined($git_p4_have)) {\n\t    $prev = $git_p4_have->{$a};\n\t    if (defined($prev)) {\n\t\t$prev->[0] =~ m!^//[^/]+/(.*)!o;\n\t\t$needup = 0 if ($b eq $1) and ($prev->[1] eq $ent->{haveRev});\n\t\tif ($needup and $VERBOSE > 1) {\n\t\t    my $reason;\n\t\t    $reason = 'local file' if $b ne $1;\n\t\t    $reason = 'revision' if $prev->[1] ne $ent->{haveRev};\n\t\t    print \"$a ($reason changed)\\n\";\n\t\t}\n\t    }\n\t}\n\tif ($needup) {\n\t    $Updated++;\n\t    push(@git_upd, $b);\n\t}\n    } else {\n\t$Added++;\n\tif ( $b =~ m/\\.(bat|cmd|pl|sh|exe|dll)$/io )\n\t{ push(@git_addx, $b) } else { push(@git_add, $b) }\n    }\n}\nclose($storedhave);\nclose($have);\nundef %p4_a_lc;\n\n@git_del = grep { !exists($p4_index{$_}) } keys %git_index;\n$Deleted = $#git_del + 1;\n\n#foreach (keys %git_index)\n#{ push(@git_del, $_) if !exists($p4_index{$_}) }\n\nif ( $DRYRUN ) {\n    print($#git_add+$#git_addx+ 2,\" files to add\\n\") if $VERBOSE;\n    print map {\" a $_\\n\"} @git_add if $VERBOSE > 2;\n    print map {\" a $_\\n\"} @git_addx if $VERBOSE > 2;\n    print($#git_del+1,\" files to unreg\\n\") if $VERBOSE;\n    print map {\" d $_\\n\"} @git_del if $VERBOSE > 2;\n    print($#git_upd+1,\" files to update\\n\") if $VERBOSE;\n    print map {\" u $_\\n\"} @git_upd if $VERBOSE > 2;\n    print \"added: $Added, unregd: $Deleted, updated: $Updated, ignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n} else {\n    if (@git_add || @git_addx) {\n\tprint($#git_add+$#git_addx+ 2,\n\t      \" files | git update-index --add -z --stdin\\n\")\n\t    if $VERBOSE;\n\tif (@git_add) {\n\t    open(GIT, '| git update-index --add --chmod=-x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_add;\n\t    close(GIT);\n\t}\n\tif (@git_addx) {\n\t    open(GIT, '| git update-index --add --chmod=+x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_addx;\n\t    close(GIT);\n\t}\n    }\n\n    if (@git_del) {\n\tprint($#git_del+1,\" files | git update-index --remove -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index --force-remove -z --stdin') or\n\t    die \"$0 git-update-index(del): $!\\n\";\n\tprint GIT map {print \" d $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_del;\n\tclose(GIT);\n    }\n\n    if (@git_upd) {\n\tprint($#git_upd+1,\" files | git update-index -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index -z --stdin') or\n\t    die \"$0 git-update-index(upd): $!\\n\";\n\tprint GIT map {print \" u $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_upd;\n\tclose(GIT);\n    }\n    \n    print \"added: $Added, unregd: $Deleted, updated: $Updated, ignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n    git_p4_commit($git_head, $git_p4_head) if $AUTO_COMMIT;\n}\n\nexit 0;\n\nsub filtered {\n    my $name = shift;\n    study($name);\n    my @path = split(/\\/+/o, $name);\n    my $dir = '';\n    $name = '';\n    \n    foreach my $d (@path) {\n\t$name .= $d;\n#\tprint STDERR \"$dir: $name $d\\n\" if $v;\n\tforeach my $re (@{$gitignore_dirs{'/'}}) {\n\t    return 1 if $name =~ m/$re/;\n\t    return 1 if $d =~ m/$re/;\n\t}\n\tif ( length($dir) and exists($gitignore_dirs{$dir}) ) {\n\t    foreach my $re (@{$gitignore_dirs{$dir}}) {\n\t\treturn 1 if $name =~ m/$re/;\n\t\treturn 1 if $d =~ m/$re/;\n\t    }\n\t}\n\t$name .= '/';\n\t$dir = $name;\n    }\n#    print STDERR \"$name not filtered\\n\" if $v;\n    return 0;\n}\n\nsub read_filter_file {\n    my @filts = ();\n    my $file = shift;\n    if ( open(my $if, '<', $file) ) {\n\tprint \"added ignore file $file\\n\" if $VERBOSE;\n\t$/ = \"\\n\";\n\twhile (my $l = <$if>) {\n\t    next if $l =~ /^\\s*#/o;\n\t    next if $l =~ /^\\s*$/o;\n\t    $l =~ s/[\\r\\n]+$//so;\n\t    $l =~ s/\\./\\\\./go;\n\t    $l =~ s/\\*/.*/go;\n\t    if ( $l =~ m/\\// ) {\n\t\t$l = \"^$l($|/)\";\n\t    } else {\n\t\t$l = \"(^|/)$l\\$\";\n\t    }\n\t    print \" filter $l\\n\" if $VERBOSE > 1;\n\t    push(@filts, qr/$l/);\n\t}\n\tclose($if);\n    }\n    return \\@filts;\n}\n\nsub r_pystr\n{\n    my $fd = shift;\n    my ($len,$str)=('','');\n    my ($c,$rd,$b) = (4,0,'');\n    while ($c > 0) {\n\t$rd = sysread($fd,$b,$c);\n\twarn(\"failed to read len: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data for len\"), return undef if !$rd;\n\t$len .= $b;\n\t$c -= $rd;\n    }\n    $len = unpack('V',$len);\n    while ($len > 0) {\n\t$rd = sysread($fd,$b,$len);\n\twarn(\"failed to read data: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data\"), return undef if !$rd;\n\t$str .= $b;\n\t$len -= $rd;\n    }\n    return $str;\n}\n\nsub read_pydict_entry\n{\n    my $f = shift;\n    my ($buf,$rd);\n    FIL: while (1) {\n\t# object type identifier\n\t$rd = sysread($f, $buf, 1);\n\tlast FIL if $rd == 0;\n\twarn(\"object type: $!\\n\"),last if $rd != 1;\n\t# '{' is a python marshalled dict\n\twarn(\"object type: not {\\n\"),last if $buf ne '{';\n\tmy $ent = {};\n\tPAIR: while (1) {\n\t    my ($b,$key);\n\t    # key type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"key type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$key = r_pystr($f);\n\t\twarn(\"key: $!\\n\"),last FIL if !defined($b);\n\t    } elsif ($b eq '0') { # NULL-element, end of entry\n\t\tlast PAIR;\n\t    } else {\n\t\twarn(\"key type: not s\");\n\t\tlast FIL;\n\t    }\n\t    # value type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"$key value type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$b = r_pystr($f);\n\t\twarn(\"$key value: $!\"),last FIL if !defined($b);\n\t\t$ent->{$key} = $b;\n\t    } else {\n\t\twarn(\"$key value type: not s ($b)\");\n\t\tlast FIL;\n\t    }\n\t}\n\treturn $ent;\n    }\n    return undef;\n}\n\nsub cl2msg {\n    my $cl = shift;\n    my($o1,$o2,$i);\n    if(!open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\twarn \"p4/msg: $!\\n\";\n\treturn;\n    }\n    binmode($o1);\n    if(!open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\twarn \"p4/p4msg: $!\\n\";\n\tclose($o1);\n\treturn\n    }\n    binmode($o2);\n    if(!open($i, '-|', \"p4 describe -s $cl\")){\n\twarn \"p4 describe: $!\\n\";\n\tclose($o1);\n\tclose($o2);\n\treturn\n    }\n    binmode($i);\n    print $o1 \"$cl: \" if $FULL_DESC;\n    print $o2 \"$cl: \";\n    my @a;\n    my $u = undef;\n    while (my $l = <$i>) {\n\tif ($l =~ /^Change \\d+ by (\\S+)@[^ ]* on ([^\\r\\n]*)/so) {\n\t    $u = $1;\n\t    $ENV{GIT_AUTHOR_DATE} = $2 if length($2);\n\t}\n\tlast if $FULL_DESC < 2 and $l =~ /^\\s*Affected files \\.{3}\\s*$/so;\n\t$l =~ s/\\r?\\n$//so;\n\tpush @a, $l;\n    }\n    close($i);\n    print $o2 substr($a[2],1),\"\\n\"; # p4 side-branch commit description\n    close($o2);\n    # import branch commit description\n    if ($FULL_DESC > 1) {\n\t# desc level 2+: keep the Change line\n\tprint $o1 map {\"$_\\n\"} (substr($a[2],1),\"\\n\",@a);\n    } else {\n\t# levels 0 and 1: remove the Change line\n\tprint $o1 map { (length($_) ? substr($_,1):'').\"\\n\" } @a[2..$#a];\n    }\n    close($o1);\n    if (defined($u)) {\n\tif (!exists($P4USERS{$u})) {\n\t    my ($mail,$name) = grep {/^(Email|FullName):/} qx{p4 user -o $u};\n\t    if ($? == 0 and defined($mail) and defined($name)) {\n\t\ts/^\\S+:\t([^\\r\\n]*)\\r?\\n$/$1/so for ($mail,$name);\n\t\tif (length($name) and length($mail)) {\n\t\t    $P4USERS{$u} = {name=>$name, email=>$mail};\n\t\t}\n\t    }\n\t}\n\tif ($P4USERS{$u}) {\n\t    $p4u = $P4USERS{$u};\n\t    $ENV{GIT_AUTHOR_NAME}  = $p4u->{name};\n\t    $ENV{GIT_AUTHOR_EMAIL} = $p4u->{email};\n\t}\n    }\n}\n\nsub git_p4_init {\n    my ($commit,$parent,$p4commit,$p4parent);\n    my ($HEAD) = qx{git rev-parse HEAD};\n    $HEAD = '' if $?;\n    my ($p4head) = qx{git rev-parse refs/p4import/$P4CLIENT};\n    $p4head = '' if $?;\n    s/\\r?\\n//gs for ($HEAD, $p4head);\n    die \"No HEAD commit! Refusing to import.\\n\" if !length($HEAD);\n    if (length($p4head)) {\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\t$p4parent = '' if !defined($p4parent);\n    } else {\n\t$commit = $p4parent = '';\n    }\n    while (($commit ne $HEAD) and length($p4parent)) {\n\t$p4head = $p4parent;\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\t$p4parent = '' if !defined($p4parent);\n\tif ($VERBOSE and ($HEAD eq $commit)) {\n\t    print \"found p4 import commit \";\n\t    system('git','name-rev',$p4head);\n\t}\n    }\n    if ($HEAD ne $commit) {\n\t$HEAD_FROM_P4 = 0;\n\twarn \"Current HEAD is not from $P4CLIENT, doing full import\\n\";\n    } else {\n\t$HEAD_FROM_P4 = 1;\n    }\n    my $p4have = undef;\n    if (!$FULL_IMPORT and ($HEAD eq $commit) and length($p4head)) {\n\tif (open(my $f, '-|', \"git cat-file blob $p4head:have\")) {\n\t    my $old = $/;\n\t    $/ = \"\\0\";\n\t    my $cnt = 0;\n\t    while(1) {\n\t\tmy $p4name = <$f>;\n\t\tlast if !defined($p4name);\n\t\t$p4name =~ s/^.//so if $cnt; # remove \\n\n\t\tmy $name = <$f>;\n\t\tmy $rev = <$f>;\n\t\tlast if !defined($name) or !defined($rev);\n\t\tchop($p4name,$name,$rev);\n\t\t++$cnt;\n\t\tif (defined($p4have)) {\n\t\t    $p4have->{$p4name} = [$name,$rev];\n\t\t} else {\n\t\t    $p4have = {$p4name=>[$name,$rev]};\n\t\t}\n\t    }\n\t    $/ = $old;\n\t    close($f);\n\t    print \"loaded $cnt revisions from $p4head\\n\" if $VERBOSE;\n\t}\n    }\n    return ($HEAD, $p4head, $p4have);\n}\n\nsub git_p4_commit {\n    my ($HEAD, $p4head) = @_;\n    my ($commit,$parent,$p4commit,$p4parent);\n\n    my ($fdo,$fdi,$rc);\n    $rc = system('git','diff-index','--exit-code','--quiet','--cached','HEAD');\n    if ($rc == 0) {\n\twarn(\"No changes\\n\");\n\treturn;\n    }\n\n    return if $DRYRUN;\n\n    if (!@DESC && !$EDIT_COMMIT) {\n\twarn \"$0: no commit description given\\n\";\n\treturn;\n    }\n\n    my $p4x = \"$GIT_DIR/p4/idx.tmp\";\n    unlink($p4x);\n\n    $ENV{PAGER} = 'cat';\n\n    if (!defined($SPEC) or !open(STDIN, '<', $SPEC)) {\n\tif ( $^O eq 'MSWin32' ) {\n\t    open(STDIN, '<', 'NUL') or die \"$SPEC: $!\\n\";\n\t} else {\n\t    open(STDIN, '<', '/dev/null') or die \"$SPEC: $!\\n\";\n\t}\n    }\n    my ($p4spec) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to store $SPEC in git repo\\n\" if $?;\n\n    open(STDIN, '<', \"$GIT_DIR/p4/client.def\") or die \"cldef: $!\\n\";\n    my ($p4clnt) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save mappings of $P4CLIENT in git repo\" if $?;\n\n    if (!defined($P4HAVE_FILE)) {\n\tprint \"reading state of $P4CLIENT\\n\" if $VERBOSE;\n\t$P4HAVE_FILE = \"$GIT_DIR/p4/have\"; \n\topen($fdo, '>', $P4HAVE_FILE) or die \"p4/have: $!\\n\";\n\tbinmode($fdo);\n\topen($fdi, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have|\") or\n\t    die \"p4 have: $!\\n\";\n\tbinmode($fdi);\n\tmy $ent;\n\twhile (defined($ent=read_pydict_entry($fdi))) {\n\t    next if !defined($ent->{depotFile});\n\t    next if !defined($ent->{clientFile});\n\t    print $fdo \"$ent->{depotFile}\\0\",\n\t\t       \"$ent->{clientFile}\\0\",\n\t\t       \"$ent->{haveRev}\\0\\n\";\n\t}\n\tclose($fdi);\n\tclose($fdo);\n    }\n\n    open(STDIN, '<', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\n    my ($p4have) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save state of $P4CLIENT in git repo\" if $?;\n\n    #\n    # Prepare commit messages\n    #\n    unlink(\"$GIT_DIR/p4/msg\", \"$GIT_DIR/p4/p4msg\");\n    open($fdo, '>', \"$GIT_DIR/p4/msg\"); close($fdo);\n    open($fdo, '>', \"$GIT_DIR/p4/p4msg\"); close($fdo);\n\n    foreach my $i (@DESC) {\n\t$i =~ s/^(.)//o;\n\tif ('c' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    cl2msg($i);\n\t} elsif ('f' eq $1) {\n\t    my($o1,$o2,$i);\n\t    if (open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\t\tif (open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\t\t    if (open($i, '<', $i)) {\n\t\t\tmy $n = 0;\n\t\t\twhile(<$i>) {\n\t\t\t    $n++;\n\t\t\t    print $o1 $_;\n\t\t\t    print $o2 $_ if $n == 1;\n\t\t\t}\n\t\t\tclose($i);\n\t\t    }\n\t\t    close($o2);\n\t\t}\n\t\tclose($o1);\n\t    }\n\t} elsif ('4' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    my ($change)=qx{p4 changes -m1 $i};\n\t    if (!defined($change) or $change !~ m/\\s+(\\d+)\\s/) {\n\t\tdie \"$i does not resolve into a change number\\n\";\n\t    }\n\t    cl2msg($1);\n\t}\n    }\n    system(\"$editor $GIT_DIR/p4/msg\") if $EDIT_COMMIT;\n\n    # copy mirror-branch commit message into side-branch\n    # commit message if no other description were given.\n    if (!-s \"$GIT_DIR/p4/p4msg\") {\n\topen($fdi, '<', \"$GIT_DIR/p4/msg\") or die \"$GIT_DIR/p4/msg: $!\\n\";\n\tsysread($fdi,$buf,-s \"$GIT_DIR/p4/msg\");\n\tclose($fdi);\n\topen($fdo, '>>', \"$GIT_DIR/p4/p4msg\") or die \"$GIT_DIR/p4/p4msg: $!\\n\";\n\tsyswrite($fdo,$buf);\n\tclose($fdo);\n    }\n\n    #\n    # Store the imported file data\n    #\n\n    if ($VERBOSE < 2) {\n\tif ( $^O eq 'MSWin32' ) { open(STDERR, \"NUL\") }\n\telse { open(STDERR, \"/dev/null\") }\n    }\n\n    my ($tree) = qx{git write-tree};\n    die \"Failed to write current tree\\n\" if $?;\n    $parent = length($HEAD) ? \"-p $HEAD\": '';\n    open(STDIN, '<', \"$GIT_DIR/p4/msg\") or die \"p4/msg: $!\\n\";\n    $tree =~ s/\\r?\\n//gs;\n    ($commit)=qx{git commit-tree $tree $parent};\n    die \"failed to commit current tree\\n\" if $?;\n    s/\\r?\\n//gs for ($commit);\n    print \"current tree stored in commit $commit\\n\" if $VERBOSE;\n\n    #\n    # Storing import control data\n    #\n    $ENV{GIT_INDEX_FILE} = $p4x;\n    open($fdo, '|-', 'git update-index --add --index-info') or\n\tdie \"could not start git update-index\\n\";\n    binmode($fdo);\n    s/\\r?\\n//gs for ($p4spec,$p4clnt,$p4have);\n    print $fdo \"100644 $p4spec\\tspec\\n\";\n    print $fdo \"100644 $p4clnt\\tclient\\n\";\n    print $fdo \"100644 $p4have\\thave\\n\";\n    close($fdo);\n    if($?) {\n\tdie \"Failed to store $SPEC in p4import index and git repo\\n\".\n\t    \"Failed to save mappings of $P4CLIENT in p4import index and git repo\\n\".\n\t    \"Failed to save state of $P4CLIENT in p4import index and git repo\\n\"\n    }\n    my ($p4tree)=qx{git write-tree};\n    die \"Failed to store $SPEC (tree) in git repo\\n\" if $?;\n\n    # Bind import control data to the file data\n    $p4parent=\"-p $commit\";\n    $p4parent=\"$p4parent -p $p4head\" if length($p4head);\n    open(STDIN, '<', \"$GIT_DIR/p4/p4msg\") or die \"p4/p4msg: $!\\n\";\n    $p4tree =~ s/\\r?\\n//gs;\n    ($p4commit)=qx{git commit-tree $p4tree $p4parent};\n    die \"Failed to store $SPEC (commit) in git repo\\n\" if $?;\n    $p4commit =~ s/\\r?\\n//gs;\n\n    # Finishing touches: update references\n    system('git','update-ref','-m','backup ref of current branch',\n\t   'p4/backup-HEAD','HEAD');\n    system('git','update-ref','-m','backup ref of p4import',\n\t   'p4/backup-p4import',\"refs/p4import/$P4CLIENT\");\n    $rc = system('git','update-ref','-m','data of p4import','HEAD',$commit);\n    die \"Failed to update HEAD\\n\" if $rc;\n    $rc = system('git','update-ref','-m','p4import',\"refs/p4import/$P4CLIENT\",$p4commit);\n    die \"Failed to store $SPEC (reference) in git repo\\n\" if $rc;\n\n    if ($VERBOSE) {\n\tprint STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev refs/p4import/$P4CLIENT}), \":\\n\";\n\tsystem('git','log','--max-count=1','--pretty=format:%h %s%n',$p4commit);\n    }\n    print STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev HEAD}), \":\\n\";\n    system('git','log','--max-count=1','--pretty=format:%h %s%n',$commit);\n}\n"},{"id":"43680","messageId":"81b0412b0705310223j15acc70fh6b7822ccf55d6089@mail.gmail.com","threadId":"7990","inReplyTo":"20070522211334.GC30871@steel.home","subject":"Re: Yet another Perforce importer","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2007-05-31T09:23:51Z","receivedAt":"2007-05-31T09:23:51Z","isPatch":false,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"Fixed another bug which could commit an empty tree.\nImproved error handling: the p4 program sends errors in the data stream,\nwhich were ignored. Now the scripts attempts to print them out.\n\n---\n\n@rem = 'NT: CMD.EXE vim: syntax=perl noet sw=4\n@perl -x -s %0 -- %*\n@exit\n@rem ';\n#!perl -w\n#line 7\n\nlocal $VERBOSE = 0;\nlocal $DRYRUN = 0;\nlocal $SHOW_DIFFS = 0;\nlocal $AUTO_COMMIT = 0;\nlocal $JUST_COMMIT = 0;\nlocal $P4CLIENT = undef;\nlocal @EDIT_COMMIT = 0;\nlocal @FULL_IMPORT = 0;\nlocal @DESC = ();\nlocal $SPEC = undef;\nlocal @P4ARGS = ();\nlocal $P4HAVE_FILE = undef;\nlocal %P4USERS = ();\nlocal $FULL_DESC = 1;\nlocal $HEAD_FROM_P4 = 0;\nlocal $P4_EDIT_CHANGED = 0;\nlocal $P4_EDIT_HEAD = undef;\n\npush(@P4ARGS, '-P', $ENV{P4PASSWD})\n    if defined($ENV{P4PASSWD}) and length($ENV{P4PASSWD});\nuse Cwd;\nlocal $start_dir = cwd();\n\nsub read_args {\n    my ($in_client, $in_cl, $in_fi, $in_p4, $no_opt) = (0,0,0,0,0);\n    foreach my $f ( @_ ) {\n\tgoto _files if $no_opt;\n\tif ($in_client) { $in_client = 0; $P4CLIENT = $f; next }\n\tif ($in_cl) { $in_cl=0; push(@DESC,\"c$f\"); next }\n\tif ($in_fi) { $in_fi=0; push(@DESC,\"f$f\"); next }\n\tif ($in_p4) { $in_p4=0; push(@DESC,\"4$f\"); next }\n\t$no_opt=1, next if $f eq '--';\n\t$DRYRUN=1, next if $f eq '-n' or $f eq '--dry-run';\n\t$SHOW_DIFFS=$DRYRUN=1, next if $f eq '--diffs';\n\t$AUTO_COMMIT=1, next if $f eq '-y' or $f eq '--yes';\n\t$JUST_COMMIT=1, next if $f eq '--just-commit';\n\t$EDIT_COMMIT=1, next if ($f eq '-e') or ($f eq '--edit');\n\t$FULL_IMPORT=1, next if $f eq '--full';\n\t$FULL_DESC++, next if $f eq '--p4-desc';\n\t$P4_EDIT_CHANGED=1, next if $f eq '--p4-edit-changed';\n\tif ($f =~ /^--p4-edit-changed=(.*)/) {\n\t    $P4_EDIT_CHANGED=1;\n\t    $P4_EDIT_HEAD=$1;\n\t    next\n\t}\n\t$VERBOSE++, next if $f eq '-v' or $f eq '--verbose';\n\t$in_client = 1, next if $f eq '--client';\n\t$P4CLIENT = $1, next if $f =~ /^--client=(.*)/;\n\t$in_cl = 1, next if $f eq '-C';\n\tpush(@DESC,\"c$1\"), next if $f =~ /^--changelist=(.*)/;\n\t$in_fi = 1, next if $f eq '-F';\n\tpush(@DESC,\"f$1\"), next if $f =~ /^--file=(.*)/;\n\t$in_p4 = 1, next if ($f eq '--ptr') or ($f eq '--p4');\n\tpush(@DESC,\"4$2\"), next if $f =~ /^--(p4|ptr)=(.*)/;\n\n\tif ($f eq '--help' or $f eq '-h') {\n\t    print <<EOF;\n$0 [-n|--dry-run] [-y|--yes] [--client <client-name>] [--diffs] \\\n[-e|--edit] [--just-commit] [--full] [-v|--verbose] [-C <change-number>] \\\n[-F <filename>] [--ptr|--p4 <p4-path-and/or-revision>] [--p4-desc] \\\n[--] [<specification>]\n\nPerforce client state importer. Creates a git commit on the current\nbranch from a state the given p4 client and working directory hold.\n\n<specification> must be given and is expected to be a file which will be\nstored on the side branch under the name \"spec\".\nRemote-to-local mapping and the revisions of files are stored in \"have\",\nand the client definition - in \"client\".\n\n--client client Specify client name (saved in .git/p4/client for the next time)\n--full          Perform full import, don't even try to figure out what changed\n-y|--yes        Commit automatically (by default only index updated)\n--just-commit   To be used after you forgot to run with --yes first time\n-n|--dry-run    Do not update the index and do not commit\n-e|--edit       Edit commit description before committing\n-v|--verbose    Be more verbose. Can be given many times, increases verbosity\n\n--file=file\n-F file         Take description for the commit from a file in the\n                next parameter\n\n--changelist=change\n-C change       Take description for the commit from this p4 change\n\n--p4|--ptr p4-path-and/or-revision\n--p4|--ptr=p4-path-and/or-revision\n                Take description for the commit from the p4 change described\n                by this p4 path, possibly including revision specification\n\n--p4-desc       Increase amount of junk from p4 change description\n\n--diffs         Show files which are different between local filesystem, index,\n                and the current HEAD. Does not do anything else\n\n--p4-edit-changed\n--p4-edit-changed=sha1\n                Merge with the given sha1 and prepare a p4 submission.\n                Current HEAD must fast-forward to sha1. If sha1 is omited,\n                the current HEAD is assumed. The working directory must\n                have no local changes.\n\nThe descriptions taken from p4 changes given by -C and --p4 will\nbe concatenated if the options given multiple times.\n\"--\" can be used to separate options from description files.\n\nEOF\n\t    exit(0);\n\t}\n\tdie \"$0: unknown option $f\\n\" if $f =~ /^-/;\n    _files:\n\twarn \"$0: spec was already set, $SPEC ignored\\n\" if defined($SPEC);\n\t$SPEC = $f;\n    }\n}\nread_args(@ARGV);\n\nlocal ($GIT_DIR) = qx{git rev-parse --git-dir};\n$GIT_DIR =~ s/\\r?\\n$//s if defined($GIT_DIR);\ndie \"$0: git directory not found\\n\" if !defined($GIT_DIR) or !-d $GIT_DIR;\n\nlocal $editor = $ENV{VISUAL};\n$editor = $ENV{EDITOR} unless defined($editor);\n$editor = 'd:/Programs/Vim/vim70/gvim.exe' unless defined($editor);\ndie \"$0: no editor defined\\n\" unless defined($editor);\n\nif ($SHOW_DIFFS) {\n    my $sep = $/;\n    $/=\"\\0\";\n    my ($show, $cnt) = (0, 0);\n    if (open(F, '-|', 'git diff-files -r --name-only -z')) {\n\twhile (<F>) {\n\t    my $c = chop;\n\t    $_ .= $c if $c ne \"\\0\";\n\t    print \"Changed files:\\n\" if !$show;\n\t    print \" $_\\n\";\n\t    $show = 1;\n\t    $cnt++;\n\t}\n\tclose(F);\n    }\n    if (open(F, '-|', 'git diff-index --cached -r -z HEAD')) {\n\t$show = 0;\n\tmy ($diff, $info) = (0, 1);\n\twhile (<F>) {\n\t    my $c = chop;\n\t    $_ .= $c if $c ne \"\\0\";\n\t    if ($info) {\n\t\tnext if !/^:(\\d{6}) (\\d{6}) ([0-9a-f]{40}) ([0-9a-f]{40}) ./o;\n\t\t# show only content changes, p4 does not support exec-bit anyway\n\t\t$diff = $3 ne $4;\n\t    } elsif ($diff) {\n\t\tprint \"Changes between index and HEAD:\\n\" if !$show;\n\t\tprint \" $_\\n\";\n\t\t$show = 1;\n\t\t$cnt++;\n\t    }\n\t    $info = !$info;\n\t}\n\tclose(F);\n    }\n    $/ = $sep;\n    exit($cnt ? 1: 0);\n}\n\n# P4 client was given in command-line. Store it\nif ( defined($P4CLIENT) ) {\n    mkdir \"$GIT_DIR/p4\", 0777;\n    if ( open(F, '>', \"$GIT_DIR/p4/client\") ) {\n\tprint F \"$P4CLIENT\\n\";\n\tclose(F);\n    } else {\n\tdie \"$0: cannot store client name: $!\\n\"\n    }\n} else {\n    if ( open(F, '<', \"$GIT_DIR/p4/client\") ) {\n\t($P4CLIENT) = <F>;\n\tclose(F);\n\t$P4CLIENT =~ s/^\\s*//,$P4CLIENT =~ s/\\s*$// if defined($P4CLIENT);\n    }\n}\ndie \"P4 client not defined\\n\" if !defined($P4CLIENT) or !length($P4CLIENT);\nprint \"reading P4 client $P4CLIENT\\n\" if $VERBOSE;\nlocal ($P4ROOT, $p4clnt, $P4HOST);\nopen(my $fdo, '>', \"$GIT_DIR/p4/client.def\") or die \"p4/client.def: $!\\n\";\nbinmode($fdo);\nopen(my $fdi, '-|', \"p4 client -o $P4CLIENT\") or die \"p4 client: $!\\n\";\nbinmode($fdi);\nmy $last_line_len = 0;\nwhile (<$fdi>) {\n    next if /^#/o;\n    if ( m/^\\s*Root:\\s*(\\S+)[\\\\\\/]*\\s*$/so ) { $P4ROOT = $1 }\n    elsif ( m/^\\s*Client:\\s*(\\S+)/o ) { $p4clnt = $1 }\n    elsif ( m/^\\s*Host:\\s*(\\S+)/o ) { $P4HOST = $1 }\n    ($VERBOSE and print), next if /^(Access|Update):/;\n    s/\\r?\\n$//so;\n    my $len = length($_);\n    print $fdo \"$_\\n\" if $len or $len != $last_line_len;\n    $last_line_len = $len;\n}\nclose($fdi);\nclose($fdo);\n\ndie \"Client root not defined\\n\" unless defined($P4ROOT);\nif ( $VERBOSE ) {\n    print \"GIT_DIR: $GIT_DIR\\n\";\n    print \"Root: $P4ROOT (cwd: $start_dir)\\n\";\n    print \"Host: $P4HOST\\n\";\n    print \"Client: $p4clnt\\n\" if $p4clnt ne $P4CLIENT;\n}\n\nif ($P4_EDIT_CHANGED) {\n    my $rc = system('git', 'diff-files', '--quiet');\n    exit(127) if $rc & 0xff; # error starting the program\n    die \"$0: there are changes in $P4ROOT. Import them first.\\n\" if $rc;\n    $P4_EDIT_HEAD = 'HEAD' if !defined($P4_EDIT_HEAD);\n    my ($mergehead) = qx{git rev-parse $P4_EDIT_HEAD};\n    exit(127) if $?;\n    exit(1) if !defined($mergehead);\n    $mergehead =~ s/\\r?\\n//gs;\n    exit(1) if !length($mergehead);\n    print \"Checking out $P4_EDIT_HEAD ($mergehead) for p4 edit\\n\" if $VERBOSE;\n    # Check if the give reference is a direct descendant of current branch\n    if ($P4_EDIT_HEAD ne 'HEAD') {\n\tmy ($sha1) = qx{git rev-list --max-count=1 $mergehead..HEAD};\n\texit(127) if $?;\n\tdie \"$0: HEAD does not fast-forward to $P4_EDIT_HEAD\\n\"\n\t    if defined($sha1) and $sha1 =~ /^[0-9a-f]{40}\\b/;\n    }\n    my $cnt;\n    my @files = ();\n    my $sep = $/;\n    $/=\"\\0\";\n    if (open(F, '-|', \"git diff-index -R --cached -r -z $mergehead\")) {\n\tmy ($diff, $info, $M) = (0, 1, '');\n\twhile (<F>) {\n\t    my $c = chop;\n\t    $_ .= $c if $c ne \"\\0\";\n\t    if ($info) {\n\t\tnext if !/^:\\d{6} \\d{6} ([0-9a-f]{40}) ([0-9a-f]{40}) (\\w+)/o;\n\t\t# use only content changes, p4 does not support exec-bit\n\t\t$diff = $1 ne $2;\n\t\t$M = $3; # change type marker\n\t    } elsif ($diff) {\n\t\tprint \"$M $_\\n\" if $VERBOSE;\n\t\tdie \"File contains characters which p4 cannot support\\n\"\n\t\t    if /[\\n@#%*]/s;\n\t\tpush @files, \"$M$_\";\n\t\t$cnt++;\n\t    }\n\t    $info = !$info;\n\t}\n\tclose(F);\n    }\n    $/ = $sep;\n    if (!$cnt) {\n\twarn \"$0: No content changes found between HEAD and $P4_EDIT_HEAD\";\n\texit(0);\n    }\n    # Create a new changelist\n    my $p4;\n    open($p4, \"p4 -c $P4CLIENT -H $P4HOST -d $P4ROOT change -o|\") or\n\tdie \"$0: failed to create changelist\\n\";\n    my @desc = map {s/\\r?\\n//so; $_} <$p4>;\n    close($p4);\n    open($p4, '>', \"$GIT_DIR/p4/changelist\") or\n\tdie \"$GIT_DIR/p4/changelist: $!\\n\";\n    foreach (@desc) {\n\tprint $p4 \"$_\\n\";\n\tif (/^Description:/o) {\n\t    my $range = \"..$mergehead\";\n\t    $range = \"${mergehead}^..$mergehead\" if $P4_EDIT_HEAD eq 'HEAD';\n\t    if (open(my $fd, '-|', \"git log $range\")) {\n\t\twhile(<$fd>) {\n\t\t    # I believe it is not possible to save this information\n\t\t    # in Perforce.\n\t\t    next if /^(commit |Author:|Date:)/;\n\t\t    s/\\r?\\n$//so;\n\t\t    next if !length($_);\n\t\t    s/^\\s+//o;\n\t\t    print $p4 \" $_\\n\";\n\t\t}\n\t\tclose($fd);\n\t    }\n\t}\n    }\n    close($p4);\n    open(STDIN, '<', \"$GIT_DIR/p4/changelist\") or\n\tdie \"$GIT_DIR/p4/changelist: $!\\n\";\n    open($p4, \"p4 -c $P4CLIENT -H $P4HOST -d $P4ROOT change -i|\") or\n\tdie \"$0: failed to create changelist\\n\";\n    my ($newchange) = grep {s/^Change (\\d+) created\\b.*/$1/so} <$p4>;\n    close($p4);\n    print \"Checking out P4 files in changelist $newchange\\n\" if $VERBOSE;\n    # open files for edit\n    $cnt = 0;\n    open($p4, '>', \"$GIT_DIR/p4/files\") or die \"$GIT_DIR/p4/files: $!\\n\";\n    print $p4 \"-c\\n$newchange\\n\";\n    print $p4 (map {++$cnt; substr($_,1).\"\\n\"} grep {/^M/} @files);\n    close($p4);\n    sub runp4 {\n\treturn system('p4','-c',$P4CLIENT,'-H',$P4HOST,'-d',$P4ROOT,@_);\n    }\n    runp4('-x',\"$GIT_DIR/p4/files\", 'edit') if $cnt;\n\n    $cnt = 0;\n    open($p4, '>', \"$GIT_DIR/p4/files\") or die \"$GIT_DIR/p4/files: $!\\n\";\n    print $p4 \"-c\\n$newchange\\n\";\n    print $p4 (map {++$cnt; substr($_,1).\"\\n\"} grep {/^A/} @files);\n    close($p4);\n    runp4('-x',\"$GIT_DIR/p4/files\", 'add') if $cnt;\n\n    $cnt = 0;\n    open($p4, '>', \"$GIT_DIR/p4/files\") or die \"$GIT_DIR/p4/files: $!\\n\";\n    print $p4 \"-c\\n$newchange\\n\";\n    print $p4 (map {++$cnt; substr($_,1).\"\\n\"} grep {/^D/} @files);\n    close($p4);\n    runp4('-x',\"$GIT_DIR/p4/files\", 'delete') if $cnt;\n\n    # p4 modifies working directory on checkout, stupid thing\n    system('git', 'update-index', '--refresh');\n    $rc = system('git', 'read-tree', '-m', '-u', $mergehead);\n    exit(127) if $rc & 0xff;\n    exit(1) if $rc;\n    print \"The state of $P4_EDIT_HEAD($mergehead) is checked out.\\n\";\n    print \"A p4 changelist $newchange is prepared.\\n\";\n    exit(0);\n}\n\nmy ($git_head,$git_p4_head,$git_p4_have) = &git_p4_init;\n\nif ($JUST_COMMIT) {\n    git_p4_commit($git_head, $git_p4_head);\n    exit 0;\n}\n\nlocal %gitignore_dirs = ();\n$gitignore_dirs{'/'} = read_filter_file(\"$GIT_DIR/info/exclude\");\npush(@{$gitignore_dirs{'/'}}, @{read_filter_file('.gitignore')});\n\nmy %git_index = ();\n$/ = \"\\0\";\nmy @git_X = ();\nprint \"Reading git file list(git ls-files @git_X --cached -z)...\\n\" if $VERBOSE;\nforeach ( qx{git ls-files @git_X --cached -z} ) {\n    chop; # chop \\0\n    next if m/^\\.gitignore$/o;\n    next if m/\\/\\.gitignore$/o;\n    next if filtered($_);\n    $git_index{$_} = 1;\n}\n\nmy @git_add = ();\nmy @git_addx = ();\nmy @git_del = ();\nmy @git_upd = ();\n\nprint \"Reading P4 file list...\\n\" if $VERBOSE;\nlocal ($Conflicts,$Ignored,$Added,$Deleted,$Updated) = (0,0,0,0,0);\n$/ = \"\\n\";\nmy $in_name = 0;\nmy @root = split(/[\\/\\\\]+/, $P4ROOT);\nmy %p4_index = ();\nmy %p4_a_lc = ();\nmy %lnames = ();\nmy %lconflicts = ();\nif (opendir(DIR, '.')) {\n    $lnames{'.'} = [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n    closedir(DIR);\n    #print \"read $start_dir (\",scalar(@{$lnames{'.'}}),\")\\n\";\n}\nopen(my $have, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have |\") or\n    die \"$0: failed to start p4: $!\\n\";\nbinmode($have);\n$P4HAVE_FILE = \"$GIT_DIR/p4/have\";\nopen(my $storedhave, '>', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\nbinmode($storedhave);\nmy ($cnt,$err,$ent) = (0,0,undef);\nwhile (defined($ent=read_pydict_entry($have))) {\n    if (defined($ent->{code}) and defined($ent->{data})) {\n\t++$err if $ent->{code} eq 'error';\n\tprint STDERR 'p4: '.$ent->{code}.': '.$ent->{data}.\"\\n\";\n\tnext;\n    }\n    next if !defined($ent->{depotFile}) or !defined($ent->{clientFile});\n    ++$cnt;\n    my $a = $ent->{depotFile};\n    $ent->{clientFile} =~ m!^//[^/]+/(.*)!o;\n    my $b = $1;\n    my @bb = split(/\\/+/, $b);\n    print $storedhave \"$a\\0$ent->{clientFile}\\0$ent->{haveRev}\\0\\n\";\n\n    if ( $^O eq 'MSWin32' ) {\n\t# stupid windows, daft activestate, dumb P4\n\t# This piece below is checking for file name conflicts\n\t# which happen on windows because of it mangling the names.\n\tmy $blc = lc $b;\n\tif ( $#bb > 0 ) {\n\t    my $path = '.';\n\t    foreach my $n (@bb[0 .. $#bb -1]) {\n\t\tmy @conflicts =\n\t\t    grep {lc $_ eq lc $n and $_ ne $n} @{$lnames{$path}};\n\t\tif (@conflicts and !exists($lconflicts{\"$path/$n\"})) {\n\t\t    warn \"warning: $a -> $b\\n\".\n\t\t\t \"warning: conflict between path \\\"$path/$n\\\" and \".\n\t\t\t \"local filesystem in \\\"@conflicts\\\"\\n\";\n\t\t    $Conflicts++;\n\t\t    $lconflicts{\"$path/$n\"} = 1;\n\t\t}\n\t\t$path .= \"/$n\";\n\t\tif (!exists($lnames{$path})) {\n\t\t    if (opendir(DIR, $path)) {\n\t\t\t$lnames{$path} =\n\t\t\t    [grep {$_ ne '.' and $_ ne '..'} readdir(DIR)];\n\t\t\tclosedir(DIR);\n\t\t\t#print \"read $path (\",scalar(@{$lnames{$path}}),\")\\n\";\n\t\t    }\n\t\t}\n\t    }\n\t}\n\tif (!exists($p4_a_lc{$blc})) {\n\t    $p4_a_lc{$blc} = [$a, $b];\n\t} else {\n\t    warn(\"warning: $a -> $b\\n\".\n\t\t \"warning: conflicts with \".\n\t\t $p4_a_lc{$blc}->[0].\" -> \".\n\t\t $p4_a_lc{$blc}->[1].\"\\n\");\n\t    $Conflicts++;\n\t    next;\n\t}\n    }\n\n    my $i;\n    for ($i = 0; $i < $#bb; ++$i) {\n\tmy $bdir = join('/',@bb[0 .. $i]) . '/';\n\tif ( !exists($gitignore_dirs{$bdir}) ) {\n\t    $gitignore_dirs{$bdir} = read_filter_file(\"$bdir.gitignore\");\n\t}\n    }\n    if (filtered($b)) {\n\tprint \" i $b\\n\" if $VERBOSE > 3;\n\t$Ignored++;\n\tnext\n    }\n    $p4_index{$b} = $a;\n    if ( exists($git_index{$b}) ) {\n\tmy $needup = 1;\n\tif (defined($git_p4_have)) {\n\t    $prev = $git_p4_have->{$a};\n\t    if (defined($prev)) {\n\t\t$prev->[0] =~ m!^//[^/]+/(.*)!o;\n\t\t$needup = 0 if ($b eq $1) and ($prev->[1] eq $ent->{haveRev});\n\t\tif ($needup and $VERBOSE > 1) {\n\t\t    my $reason;\n\t\t    $reason = 'local file' if $b ne $1;\n\t\t    $reason = 'revision' if $prev->[1] ne $ent->{haveRev};\n\t\t    print \"$a ($reason changed)\\n\";\n\t\t}\n\t    }\n\t}\n\tif ($needup) {\n\t    $Updated++;\n\t    push(@git_upd, $b);\n\t}\n    } else {\n\t$Added++;\n\tif ( $b =~ m/\\.(bat|cmd|pl|sh|exe|dll)$/io )\n\t{ push(@git_addx, $b) } else { push(@git_add, $b) }\n    }\n}\nclose($storedhave);\nclose($have);\nexit 1 if $err; # the error already reported\ndie \"Nothing in the client $P4CLIENT\\n\" if !$cnt;\n\nundef %p4_a_lc;\n\n@git_del = grep { !exists($p4_index{$_}) } keys %git_index;\n$Deleted = $#git_del + 1;\n\n#foreach (keys %git_index)\n#{ push(@git_del, $_) if !exists($p4_index{$_}) }\n\nif ( $DRYRUN ) {\n    print($#git_add+$#git_addx+ 2,\" files to add\\n\") if $VERBOSE;\n    print map {\" a $_\\n\"} @git_add if $VERBOSE > 2;\n    print map {\" a $_\\n\"} @git_addx if $VERBOSE > 2;\n    print($#git_del+1,\" files to unreg\\n\") if $VERBOSE;\n    print map {\" d $_\\n\"} @git_del if $VERBOSE > 2;\n    print($#git_upd+1,\" files to update\\n\") if $VERBOSE;\n    print map {\" u $_\\n\"} @git_upd if $VERBOSE > 2;\n    print \"added: $Added, unregd: $Deleted, updated: $Updated,\nignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n} else {\n    if (@git_add || @git_addx) {\n\tprint($#git_add+$#git_addx+ 2,\n\t      \" files | git update-index --add -z --stdin\\n\")\n\t    if $VERBOSE;\n\tif (@git_add) {\n\t    open(GIT, '| git update-index --add --chmod=-x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_add;\n\t    close(GIT);\n\t}\n\tif (@git_addx) {\n\t    open(GIT, '| git update-index --add --chmod=+x -z --stdin') or\n\t\tdie \"$0 git-update-index(add): $!\\n\";\n\t    print GIT map {print \" a $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_addx;\n\t    close(GIT);\n\t}\n    }\n\n    if (@git_del) {\n\tprint($#git_del+1,\" files | git update-index --remove -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index --force-remove -z --stdin') or\n\t    die \"$0 git-update-index(del): $!\\n\";\n\tprint GIT map {print \" d $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_del;\n\tclose(GIT);\n    }\n\n    if (@git_upd) {\n\tprint($#git_upd+1,\" files | git update-index -z --stdin\\n\")\n\t    if $VERBOSE;\n\topen(GIT, '| git update-index -z --stdin') or\n\t    die \"$0 git-update-index(upd): $!\\n\";\n\tprint GIT map {print \" u $_\\n\" if $VERBOSE > 1; \"$_\\0\"} @git_upd;\n\tclose(GIT);\n    }\n\n    print \"added: $Added, unregd: $Deleted, updated: $Updated,\nignored: $Ignored\";\n    print \", conflicts: $Conflicts\" if $Conflicts;\n    print \"\\n\";\n    git_p4_commit($git_head, $git_p4_head) if $AUTO_COMMIT;\n}\n\nexit 0;\n\nsub filtered {\n    my $name = shift;\n    study($name);\n    my @path = split(/\\/+/o, $name);\n    my $dir = '';\n    $name = '';\n\n    foreach my $d (@path) {\n\t$name .= $d;\n#\tprint STDERR \"$dir: $name $d\\n\" if $v;\n\tforeach my $re (@{$gitignore_dirs{'/'}}) {\n\t    return 1 if $name =~ m/$re/;\n\t    return 1 if $d =~ m/$re/;\n\t}\n\tif ( length($dir) and exists($gitignore_dirs{$dir}) ) {\n\t    foreach my $re (@{$gitignore_dirs{$dir}}) {\n\t\treturn 1 if $name =~ m/$re/;\n\t\treturn 1 if $d =~ m/$re/;\n\t    }\n\t}\n\t$name .= '/';\n\t$dir = $name;\n    }\n#    print STDERR \"$name not filtered\\n\" if $v;\n    return 0;\n}\n\nsub read_filter_file {\n    my @filts = ();\n    my $file = shift;\n    if ( open(my $if, '<', $file) ) {\n\tprint \"added ignore file $file\\n\" if $VERBOSE;\n\t$/ = \"\\n\";\n\twhile (my $l = <$if>) {\n\t    next if $l =~ /^\\s*#/o;\n\t    next if $l =~ /^\\s*$/o;\n\t    $l =~ s/[\\r\\n]+$//so;\n\t    $l =~ s/\\./\\\\./go;\n\t    $l =~ s/\\*/.*/go;\n\t    if ( $l =~ m/\\// ) {\n\t\t$l = \"^$l($|/)\";\n\t    } else {\n\t\t$l = \"(^|/)$l\\$\";\n\t    }\n\t    print \" filter $l\\n\" if $VERBOSE > 1;\n\t    push(@filts, qr/$l/);\n\t}\n\tclose($if);\n    }\n    return \\@filts;\n}\n\nsub r_pystr\n{\n    my $fd = shift;\n    my ($len,$str)=('','');\n    my ($c,$rd,$b) = (4,0,'');\n    while ($c > 0) {\n\t$rd = sysread($fd,$b,$c);\n\twarn(\"failed to read len: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data for len\"), return undef if !$rd;\n\t$len .= $b;\n\t$c -= $rd;\n    }\n    $len = unpack('V',$len);\n    while ($len > 0) {\n\t$rd = sysread($fd,$b,$len);\n\twarn(\"failed to read data: $!\"), return undef if !defined($rd);\n\twarn(\"not enough data\"), return undef if !$rd;\n\t$str .= $b;\n\t$len -= $rd;\n    }\n    return $str;\n}\n\nsub read_pydict_entry\n{\n    my $f = shift;\n    my ($buf,$rd);\n    FIL: while (1) {\n\t# object type identifier\n\t$rd = sysread($f, $buf, 1);\n\tlast FIL if $rd == 0;\n\twarn(\"p4: object type: $!\\n\"),last if $rd != 1;\n\t# '{' is a python marshalled dict\n\twarn(\"p4: object type: not {\\n\"),last if $buf ne '{';\n\tmy $ent = {};\n\tPAIR: while (1) {\n\t    my ($b,$key);\n\t    # key type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"p4: key type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$key = r_pystr($f);\n\t\twarn(\"p4: key: $!\\n\"),last FIL if !defined($b);\n\t    } elsif ($b eq '0') { # NULL-element, end of entry\n\t\tlast PAIR;\n\t    } else {\n\t\tdie(\"p4: key type: not s (string)\\n\");\n\t\tlast FIL;\n\t    }\n\t    # value type identifier\n\t    $rd = sysread($f, $b, 1);\n\t    warn(\"p4: $key value type: $!\\n\"),last FIL if $rd != 1;\n\t    if ($b eq 's') { # length-prefixed string\n\t\t$b = r_pystr($f);\n\t\twarn(\"p4: $key value: $!\\n\"),last FIL if !defined($b);\n\t\t$ent->{$key} = $b;\n\t    } elsif ($b eq 'i') { # 4-byte integer\n\t\t$rd = sysread($f, $b, 4);\n\t\twarn(\"p4: $key value data: $!\\n\"),last FIL if $rd != 4;\n\t\t$ent->{$key} = $b;\n\t    } else {\n\t\twarn(\"p4: $key value type: not s ($b)\\n\");\n\t\tlast FIL;\n\t    }\n\t}\n\treturn $ent;\n    }\n    return undef;\n}\n\nsub cl2msg {\n    my $cl = shift;\n    my($o1,$o2,$i);\n    if(!open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\twarn \"p4/msg: $!\\n\";\n\treturn;\n    }\n    binmode($o1);\n    if(!open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\twarn \"p4/p4msg: $!\\n\";\n\tclose($o1);\n\treturn\n    }\n    binmode($o2);\n    if(!open($i, '-|', \"p4 describe -s $cl\")){\n\twarn \"p4 describe: $!\\n\";\n\tclose($o1);\n\tclose($o2);\n\treturn\n    }\n    binmode($i);\n    print $o1 \"$cl: \" if $FULL_DESC;\n    print $o2 \"$cl: \";\n    my @a;\n    my $u = undef;\n    while (my $l = <$i>) {\n\tif ($l =~ /^Change \\d+ by (\\S+)@[^ ]* on ([^\\r\\n]*)/so) {\n\t    $u = $1;\n\t    $ENV{GIT_AUTHOR_DATE} = $2 if length($2);\n\t}\n\tlast if $FULL_DESC < 2 and $l =~ /^\\s*Affected files \\.{3}\\s*$/so;\n\t$l =~ s/\\r?\\n$//so;\n\tpush @a, $l;\n    }\n    close($i);\n    print $o2 substr($a[2],1),\"\\n\"; # p4 side-branch commit description\n    close($o2);\n    # import branch commit description\n    if ($FULL_DESC > 1) {\n\t# desc level 2+: keep the Change line\n\tprint $o1 map {\"$_\\n\"} (substr($a[2],1),\"\\n\",@a);\n    } else {\n\t# levels 0 and 1: remove the Change line\n\tprint $o1 map { (length($_) ? substr($_,1):'').\"\\n\" } @a[2..$#a];\n    }\n    close($o1);\n    if (defined($u)) {\n\tif (!exists($P4USERS{$u})) {\n\t    my ($mail,$name) = grep {/^(Email|FullName):/} qx{p4 user -o $u};\n\t    if ($? == 0 and defined($mail) and defined($name)) {\n\t\ts/^\\S+:\t([^\\r\\n]*)\\r?\\n$/$1/so for ($mail,$name);\n\t\tif (length($name) and length($mail)) {\n\t\t    $P4USERS{$u} = {name=>$name, email=>$mail};\n\t\t}\n\t    }\n\t}\n\tif ($P4USERS{$u}) {\n\t    $p4u = $P4USERS{$u};\n\t    $ENV{GIT_AUTHOR_NAME}  = $p4u->{name};\n\t    $ENV{GIT_AUTHOR_EMAIL} = $p4u->{email};\n\t}\n    }\n}\n\nsub git_p4_init {\n    my ($commit,$parent,$p4commit,$p4parent);\n    my ($HEAD) = qx{git rev-parse HEAD};\n    $HEAD = '' if $?;\n    my ($p4head) = qx{git rev-parse refs/p4import/$P4CLIENT};\n    $p4head = '' if $?;\n    s/\\r?\\n//gs for ($HEAD, $p4head);\n    die \"No HEAD commit! Refusing to import.\\n\" if !length($HEAD);\n    if (length($p4head)) {\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\t$p4parent = '' if !defined($p4parent);\n    } else {\n\t$commit = $p4parent = '';\n    }\n    while (($commit ne $HEAD) and length($p4parent)) {\n\t$p4head = $p4parent;\n\t($commit,$p4parent) =\n\t    grep { s/^parent (.{40}).*/$1/s }\n\t    qx{git cat-file commit $p4head};\n\t$commit = $p4parent = '' if $?;\n\t$p4parent = '' if !defined($p4parent);\n\tif ($VERBOSE and ($HEAD eq $commit)) {\n\t    print \"found p4 import commit \";\n\t    system('git','name-rev',$p4head);\n\t}\n    }\n    if ($HEAD ne $commit) {\n\t$HEAD_FROM_P4 = 0;\n\twarn \"Current HEAD is not from $P4CLIENT, doing full import\\n\";\n    } else {\n\t$HEAD_FROM_P4 = 1;\n    }\n    my $p4have = undef;\n    if (!$FULL_IMPORT and ($HEAD eq $commit) and length($p4head)) {\n\tif (open(my $f, '-|', \"git cat-file blob $p4head:have\")) {\n\t    my $old = $/;\n\t    $/ = \"\\0\";\n\t    my $cnt = 0;\n\t    while(1) {\n\t\tmy $p4name = <$f>;\n\t\tlast if !defined($p4name);\n\t\t$p4name =~ s/^.//so if $cnt; # remove \\n\n\t\tmy $name = <$f>;\n\t\tmy $rev = <$f>;\n\t\tlast if !defined($name) or !defined($rev);\n\t\tchop($p4name,$name,$rev);\n\t\t++$cnt;\n\t\tif (defined($p4have)) {\n\t\t    $p4have->{$p4name} = [$name,$rev];\n\t\t} else {\n\t\t    $p4have = {$p4name=>[$name,$rev]};\n\t\t}\n\t    }\n\t    $/ = $old;\n\t    close($f);\n\t    print \"loaded $cnt revisions from $p4head\\n\" if $VERBOSE;\n\t}\n    }\n    return ($HEAD, $p4head, $p4have);\n}\n\nsub git_p4_commit {\n    my ($HEAD, $p4head) = @_;\n    my ($commit,$parent,$p4commit,$p4parent);\n\n    my ($fdo,$fdi,$rc);\n    $rc = system('git','diff-index','--exit-code','--quiet','--cached','HEAD');\n    if ($rc == 0) {\n\twarn(\"No changes\\n\");\n\treturn;\n    }\n\n    return if $DRYRUN;\n\n    if (!@DESC && !$EDIT_COMMIT) {\n\twarn \"$0: no commit description given\\n\";\n\treturn;\n    }\n\n    my $p4x = \"$GIT_DIR/p4/idx.tmp\";\n    unlink($p4x);\n\n    $ENV{PAGER} = 'cat';\n\n    if (!defined($SPEC) or !open(STDIN, '<', $SPEC)) {\n\tif ( $^O eq 'MSWin32' ) {\n\t    open(STDIN, '<', 'NUL') or die \"$SPEC: $!\\n\";\n\t} else {\n\t    open(STDIN, '<', '/dev/null') or die \"$SPEC: $!\\n\";\n\t}\n    }\n    my ($p4spec) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to store $SPEC in git repo\\n\" if $?;\n\n    open(STDIN, '<', \"$GIT_DIR/p4/client.def\") or die \"cldef: $!\\n\";\n    my ($p4clnt) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save mappings of $P4CLIENT in git repo\" if $?;\n\n    if (!defined($P4HAVE_FILE)) {\n\tprint \"reading state of $P4CLIENT\\n\" if $VERBOSE;\n\t$P4HAVE_FILE = \"$GIT_DIR/p4/have\";\n\topen($fdo, '>', $P4HAVE_FILE) or die \"p4/have: $!\\n\";\n\tbinmode($fdo);\n\topen($fdi, \"p4 -G @P4ARGS -c $P4CLIENT -H $P4HOST -d $P4ROOT have|\") or\n\t    die \"p4 have: $!\\n\";\n\tbinmode($fdi);\n\tmy ($cnt,$err,$ent) = (0,0,undef);\n\twhile (defined($ent=read_pydict_entry($fdi))) {\n\t    if (defined($ent->{code}) and defined($ent->{data})) {\n\t\t++$err if $ent->{code} eq 'error';\n\t\tprint STDERR 'p4: '.$ent->{code}.': '.$ent->{data}.\"\\n\";\n\t\tnext;\n\t    }\n\t    next if !defined($ent->{depotFile});\n\t    next if !defined($ent->{clientFile});\n\t    ++$cnt;\n\t    print $fdo \"$ent->{depotFile}\\0\",\n\t\t       \"$ent->{clientFile}\\0\",\n\t\t       \"$ent->{haveRev}\\0\\n\";\n\t}\n\tclose($fdi);\n\tclose($fdo);\n\texit 1 if $err; # the error already reported\n\tdie \"The client $P4CLIENT has nothing\\n\" if !$cnt;\n    }\n\n    open(STDIN, '<', $P4HAVE_FILE) or die \"$P4HAVE_FILE: $!\\n\";\n    my ($p4have) = qx{git hash-object -t blob -w --stdin};\n    die \"Failed to save state of $P4CLIENT in git repo\" if $?;\n\n    #\n    # Prepare commit messages\n    #\n    unlink(\"$GIT_DIR/p4/msg\", \"$GIT_DIR/p4/p4msg\");\n    open($fdo, '>', \"$GIT_DIR/p4/msg\"); close($fdo);\n    open($fdo, '>', \"$GIT_DIR/p4/p4msg\"); close($fdo);\n\n    foreach my $i (@DESC) {\n\t$i =~ s/^(.)//o;\n\tif ('c' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    cl2msg($i);\n\t} elsif ('f' eq $1) {\n\t    my($o1,$o2,$i);\n\t    if (open($o1, '>>', \"$GIT_DIR/p4/msg\")) {\n\t\tif (open($o2, '>>', \"$GIT_DIR/p4/p4msg\")) {\n\t\t    if (open($i, '<', $i)) {\n\t\t\tmy $n = 0;\n\t\t\twhile(<$i>) {\n\t\t\t    $n++;\n\t\t\t    print $o1 $_;\n\t\t\t    print $o2 $_ if $n == 1;\n\t\t\t}\n\t\t\tclose($i);\n\t\t    }\n\t\t    close($o2);\n\t\t}\n\t\tclose($o1);\n\t    }\n\t} elsif ('4' eq $1) {\n\t    print \"reading changes for $i\\n\" if $VERBOSE;\n\t    my ($change)=qx{p4 changes -m1 $i};\n\t    if (!defined($change) or $change !~ m/\\s+(\\d+)\\s/) {\n\t\tdie \"$i does not resolve into a change number\\n\";\n\t    }\n\t    cl2msg($1);\n\t}\n    }\n    system(\"$editor $GIT_DIR/p4/msg\") if $EDIT_COMMIT;\n\n    # copy mirror-branch commit message into side-branch\n    # commit message if no other description were given.\n    if (!-s \"$GIT_DIR/p4/p4msg\") {\n\topen($fdi, '<', \"$GIT_DIR/p4/msg\") or die \"$GIT_DIR/p4/msg: $!\\n\";\n\tsysread($fdi,$buf,-s \"$GIT_DIR/p4/msg\");\n\tclose($fdi);\n\topen($fdo, '>>', \"$GIT_DIR/p4/p4msg\") or die \"$GIT_DIR/p4/p4msg: $!\\n\";\n\tsyswrite($fdo,$buf);\n\tclose($fdo);\n    }\n\n    #\n    # Store the imported file data\n    #\n\n    if ($VERBOSE < 2) {\n\tif ( $^O eq 'MSWin32' ) { open(STDERR, \"NUL\") }\n\telse { open(STDERR, \"/dev/null\") }\n    }\n\n    my ($tree) = qx{git write-tree};\n    die \"Failed to write current tree\\n\" if $?;\n    $parent = length($HEAD) ? \"-p $HEAD\": '';\n    open(STDIN, '<', \"$GIT_DIR/p4/msg\") or die \"p4/msg: $!\\n\";\n    $tree =~ s/\\r?\\n//gs;\n    ($commit)=qx{git commit-tree $tree $parent};\n    die \"failed to commit current tree\\n\" if $?;\n    s/\\r?\\n//gs for ($commit);\n    print \"current tree stored in commit $commit\\n\" if $VERBOSE;\n\n    #\n    # Storing import control data\n    #\n    $ENV{GIT_INDEX_FILE} = $p4x;\n    open($fdo, '|-', 'git update-index --add --index-info') or\n\tdie \"could not start git update-index\\n\";\n    binmode($fdo);\n    s/\\r?\\n//gs for ($p4spec,$p4clnt,$p4have);\n    print $fdo \"100644 $p4spec\\tspec\\n\";\n    print $fdo \"100644 $p4clnt\\tclient\\n\";\n    print $fdo \"100644 $p4have\\thave\\n\";\n    close($fdo);\n    if($?) {\n\tdie \"Failed to store $SPEC in p4import index and git repo\\n\".\n\t    \"Failed to save mappings of $P4CLIENT in p4import index and git repo\\n\".\n\t    \"Failed to save state of $P4CLIENT in p4import index and git repo\\n\"\n    }\n    my ($p4tree)=qx{git write-tree};\n    die \"Failed to store $SPEC (tree) in git repo\\n\" if $?;\n\n    # Bind import control data to the file data\n    $p4parent=\"-p $commit\";\n    $p4parent=\"$p4parent -p $p4head\" if length($p4head);\n    open(STDIN, '<', \"$GIT_DIR/p4/p4msg\") or die \"p4/p4msg: $!\\n\";\n    $p4tree =~ s/\\r?\\n//gs;\n    ($p4commit)=qx{git commit-tree $p4tree $p4parent};\n    die \"Failed to store $SPEC (commit) in git repo\\n\" if $?;\n    $p4commit =~ s/\\r?\\n//gs;\n\n    # Finishing touches: update references\n    system('git','update-ref','-m','backup ref of current branch',\n\t   'p4/backup-HEAD','HEAD');\n    system('git','update-ref','-m','backup ref of p4import',\n\t   'p4/backup-p4import',\"refs/p4import/$P4CLIENT\");\n    $rc = system('git','update-ref','-m','data of p4import','HEAD',$commit);\n    die \"Failed to update HEAD\\n\" if $rc;\n    $rc = system('git','update-ref','-m','p4import',\"refs/p4import/$P4CLIENT\",$p4commit);\n    die \"Failed to store $SPEC (reference) in git repo\\n\" if $rc;\n\n    if ($VERBOSE) {\n\tprint STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev\nrefs/p4import/$P4CLIENT}), \":\\n\";\n\tsystem('git','log','--max-count=1','--pretty=format:%h %s%n',$p4commit);\n    }\n    print STDOUT (grep {s/\\r?\\n//gs;s/.*?\\s//} qx{git name-rev HEAD}), \":\\n\";\n    system('git','log','--max-count=1','--pretty=format:%h %s%n',$commit);\n}\n"}]}