sub req_update()

in git-cvsserver.perl [1173:1517]


sub req_update
{
    my ( $cmd, $data ) = @_;

    $log->debug("req_update : " . ( defined($data) ? $data : "[NULL]" ));

    argsplit("update");

    #
    # It may just be a client exploring the available heads/modules
    # in that case, list them as top level directories and leave it
    # at that. Eclipse uses this technique to offer you a list of
    # projects (heads in this case) to checkout.
    #
    if ($state->{module} eq '') {
        my $showref = safe_pipe_capture(qw(git show-ref --heads));
        print "E cvs update: Updating .\n";
        for my $line (split '\n', $showref) {
            if ( $line =~ m% refs/heads/(.*)$% ) {
                print "E cvs update: New directory `$1'\n";
            }
        }
        print "ok\n";
        return 1;
    }


    # Grab a handle to the SQLite db and do any necessary updates
    my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log);

    $updater->update();

    argsfromdir($updater);

    #$log->debug("update state : " . Dumper($state));

    my($repoDir);
    $repoDir=$state->{CVSROOT} . "/$state->{module}/$state->{prependdir}";

    my %seendirs = ();

    # foreach file specified on the command line ...
    foreach my $argsFilename ( @{$state->{args}} )
    {
        my $filename;
        $filename = filecleanup($argsFilename);

        $log->debug("Processing file $filename");

        # if we have a -C we should pretend we never saw modified stuff
        if ( exists ( $state->{opt}{C} ) )
        {
            delete $state->{entries}{$filename}{modified_hash};
            delete $state->{entries}{$filename}{modified_filename};
            $state->{entries}{$filename}{unchanged} = 1;
        }

        my $stickyInfo = resolveStickyInfo($filename,
                                           $state->{opt}{r},
                                           $state->{opt}{D},
                                           exists($state->{opt}{A}));
        my $meta = $updater->getmeta($filename, $stickyInfo);

        # If -p was given, "print" the contents of the requested revision.
        if ( exists ( $state->{opt}{p} ) ) {
            if ( defined ( $meta->{revision} ) ) {
                $log->info("Printing '$filename' revision " . $meta->{revision});

                transmitfile($meta->{filehash}, { print => 1 });
            }

            next;
        }

        # Directories:
        prepDirForOutput(
                dirname($argsFilename),
                $repoDir,
                ".",
                \%seendirs,
                "update",
                $state->{dirArgs} );

        my $wrev = revparse($filename);

	if ( ! defined $meta )
	{
	    $meta = {
	        name => $filename,
	        revision => '0',
	        filehash => 'added'
	    };
	    if($wrev ne "0")
	    {
	        $meta->{filehash}='deleted';
	    }
	}

        my $oldmeta = $meta;

        # If the working copy is an old revision, lets get that version too for comparison.
        my $oldWrev=$wrev;
        if(defined($oldWrev))
        {
            $oldWrev=~s/^-//;
            if($oldWrev ne $meta->{revision})
            {
                $oldmeta = $updater->getmeta($filename, $oldWrev);
            }
        }

        #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev");

        # Files are up to date if the working copy and repo copy have the same revision,
        # and the working copy is unmodified _and_ the user hasn't specified -C
        next if ( defined ( $wrev )
                  and defined($meta->{revision})
                  and $wrev eq $meta->{revision}
                  and $state->{entries}{$filename}{unchanged}
                  and not exists ( $state->{opt}{C} ) );

        # If the working copy and repo copy have the same revision,
        # but the working copy is modified, tell the client it's modified
        if ( defined ( $wrev )
             and defined($meta->{revision})
             and $wrev eq $meta->{revision}
             and $wrev ne "0"
             and defined($state->{entries}{$filename}{modified_hash})
             and not exists ( $state->{opt}{C} ) )
        {
            $log->info("Tell the client the file is modified");
            print "MT text M \n";
            print "MT fname $filename\n";
            print "MT newline\n";
            next;
        }

        if ( $meta->{filehash} eq "deleted" && $wrev ne "0" )
        {
            # TODO: If it has been modified in the sandbox, error out
            #   with the appropriate message, rather than deleting a modified
            #   file.

            my ( $filepart, $dirpart ) = filenamesplit($filename,1);

            $log->info("Removing '$filename' from working copy (no longer in the repo)");

            print "E cvs update: `$filename' is no longer in the repository\n";
            # Don't want to actually _DO_ the update if -n specified
            unless ( $state->{globaloptions}{-n} ) {
		print "Removed $dirpart\n";
		print "$filepart\n";
	    }
        }
        elsif ( not defined ( $state->{entries}{$filename}{modified_hash} )
		or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash}
		or $meta->{filehash} eq 'added' )
        {
            # normal update, just send the new revision (either U=Update,
            # or A=Add, or R=Remove)
	    if ( defined($wrev) && ($wrev=~/^-/) )
	    {
	        $log->info("Tell the client the file is scheduled for removal");
		print "MT text R \n";
                print "MT fname $filename\n";
                print "MT newline\n";
		next;
	    }
	    elsif ( (!defined($wrev) || $wrev eq '0') &&
                    (!defined($meta->{revision}) || $meta->{revision} eq '0') )
	    {
	        $log->info("Tell the client the file is scheduled for addition");
		print "MT text A \n";
                print "MT fname $filename\n";
                print "MT newline\n";
		next;

	    }
	    else {
                $log->info("UpdatingX3 '$filename' to ".$meta->{revision});
                print "MT +updated\n";
                print "MT text U \n";
                print "MT fname $filename\n";
                print "MT newline\n";
		print "MT -updated\n";
	    }

            my ( $filepart, $dirpart ) = filenamesplit($filename,1);

	    # Don't want to actually _DO_ the update if -n specified
	    unless ( $state->{globaloptions}{-n} )
	    {
		if ( defined ( $wrev ) )
		{
		    # instruct client we're sending a file to put in this path as a replacement
		    print "Update-existing $dirpart\n";
		    $log->debug("Updating existing file 'Update-existing $dirpart'");
		} else {
		    # instruct client we're sending a file to put in this path as a new file

		    $log->debug("Creating new file 'Created $dirpart'");
		    print "Created $dirpart\n";
		}
		print $state->{CVSROOT} . "/$state->{module}/$filename\n";

		# this is an "entries" line
		my $kopts = kopts_from_path($filename,"sha1",$meta->{filehash});
                my $entriesLine = "/$filepart/$meta->{revision}//$kopts/";
                $entriesLine .= getStickyTagOrDate($stickyInfo);
		$log->debug($entriesLine);
		print "$entriesLine\n";

		# permissions
		$log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
		print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";

		# transmit file
		transmitfile($meta->{filehash});
	    }
        } else {
            my ( $filepart, $dirpart ) = filenamesplit($meta->{name},1);

            my $mergeDir = setupTmpDir();

            my $file_local = $filepart . ".mine";
            my $mergedFile = "$mergeDir/$file_local";
            system("ln","-s",$state->{entries}{$filename}{modified_filename}, $file_local);
            my $file_old = $filepart . "." . $oldmeta->{revision};
            transmitfile($oldmeta->{filehash}, { targetfile => $file_old });
            my $file_new = $filepart . "." . $meta->{revision};
            transmitfile($meta->{filehash}, { targetfile => $file_new });

            # we need to merge with the local changes ( M=successful merge, C=conflict merge )
            $log->info("Merging $file_local, $file_old, $file_new");
            print "M Merging differences between $oldmeta->{revision} and $meta->{revision} into $filename\n";

            $log->debug("Temporary directory for merge is $mergeDir");

            my $return = system("git", "merge-file", $file_local, $file_old, $file_new);
            $return >>= 8;

            cleanupTmpDir();

            if ( $return == 0 )
            {
                $log->info("Merged successfully");
                print "M M $filename\n";
                $log->debug("Merged $dirpart");

                # Don't want to actually _DO_ the update if -n specified
                unless ( $state->{globaloptions}{-n} )
                {
                    print "Merged $dirpart\n";
                    $log->debug($state->{CVSROOT} . "/$state->{module}/$filename");
                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
                    my $kopts = kopts_from_path("$dirpart/$filepart",
                                                "file",$mergedFile);
                    $log->debug("/$filepart/$meta->{revision}//$kopts/");
                    my $entriesLine="/$filepart/$meta->{revision}//$kopts/";
                    $entriesLine .= getStickyTagOrDate($stickyInfo);
                    print "$entriesLine\n";
                }
            }
            elsif ( $return == 1 )
            {
                $log->info("Merged with conflicts");
                print "E cvs update: conflicts found in $filename\n";
                print "M C $filename\n";

                # Don't want to actually _DO_ the update if -n specified
                unless ( $state->{globaloptions}{-n} )
                {
                    print "Merged $dirpart\n";
                    print $state->{CVSROOT} . "/$state->{module}/$filename\n";
                    my $kopts = kopts_from_path("$dirpart/$filepart",
                                                "file",$mergedFile);
                    my $entriesLine = "/$filepart/$meta->{revision}/+/$kopts/";
                    $entriesLine .= getStickyTagOrDate($stickyInfo);
                    print "$entriesLine\n";
                }
            }
            else
            {
                $log->warn("Merge failed");
                next;
            }

            # Don't want to actually _DO_ the update if -n specified
            unless ( $state->{globaloptions}{-n} )
            {
                # permissions
                $log->debug("SEND : u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}");
                print "u=$meta->{mode},g=$meta->{mode},o=$meta->{mode}\n";

                # transmit file, format is single integer on a line by itself (file
                # size) followed by the file contents
                # TODO : we should copy files in blocks
                my $data = safe_pipe_capture('cat', $mergedFile);
                $log->debug("File size : " . length($data));
                print length($data) . "\n";
                print $data;
            }
        }

    }

    # prepDirForOutput() any other existing directories unless they already
    # have the right sticky tag:
    unless ( $state->{globaloptions}{n} )
    {
        my $dir;
        foreach $dir (keys(%{$state->{dirMap}}))
        {
            if( ! $seendirs{$dir} &&
                exists($state->{dirArgs}{$dir}) )
            {
                my($oldTag);
                $oldTag=$state->{dirMap}{$dir}{tagspec};

                unless( ( exists($state->{opt}{A}) &&
                          defined($oldTag) ) ||
                          ( defined($state->{opt}{r}) &&
                            ( !defined($oldTag) ||
                              $state->{opt}{r} ne $oldTag ) ) )
                        # TODO?: OR sticky dir is different...
                {
                    next;
                }

                prepDirForOutput(
                        $dir,
                        $repoDir,
                        ".",
                        \%seendirs,
                        'update',
                        $state->{dirArgs} );
            }

            # TODO?: Consider sending a final duplicate Sticky response
            #   to more closely mimic real CVS.
        }
    }

    print "ok\n";
}