[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: SVNCopy - move/copy/sync WCs...

From: Thomas Hruska <thruska_at_cubiclesoft.com>
Date: 2006-07-21 20:48:12 CEST

David James wrote:
> On 7/19/06, Thomas Hruska <thruska@cubiclesoft.com> wrote:
>> This tool allows me to copy just the files of a large WC to my laptop in
>> 3 seconds or less from my PC - including all changes. Then I can take
>> this anywhere - a meeting, lunch, outside, a business trip, etc. When I
>> return, I use SVNCopy again to copy the WC back to the PC from the
>> laptop - again only taking a couple seconds. Neither of these
>> operations consumes a revision.
>>
>> If someone accidentally edits both WCs, SVNCopy attempts to merge/sync
>> the two WCs before performing the main operation. Essentially, it is
>> unlikely to fail in even accidental editing scenarios.
>>
>> I'm willing to share my source code if this is a feature you guys are
>> interested in developing into an API that will be able to trickle down
>> to TSVN. A couple of commercial companies that I've approached that use
>> TSVN are quite interested in SVNCopy and a number of developers are
>> already using it. Essentially, SVNCopy makes it possible to create
>> "mobile WCs".
>>
>> Given that this is just a proof-of-concept tool and is already
>> moderately popular, I would love to see something similar show up in
>> Subversion and ultimately TortoiseSVN as an official feature.
>
> Thomas,
>
> This sounds like a very useful tool! How does it work?
>
> Do you sync the raw files only, or do you also sync the working copy
> metadata (i.e. the files in the .svn metadirs)? How do you identify
> which files are modified in the WC, and in the .svn dirs? For files
> which are identified as modified, do you simply copy them over, or do
> you do block-by-block synchronization ala rsync?
>
> You mentioned that your tool supports automatic merging of WCs that
> are edited on both machines. How do you deal with the case where the
> same file is modified on both machines?
>
> Cheers,
>
> David

The readme.txt file describes most of the process and the program
describes what it is doing as it does it.

The first version of SVNCopy did a 'svn revert' on the destination and
then 'svn stat' on the source. Then each .svn directory that had
changes (according to 'svn stat') was 'xcopy'ed from the source to the
destination along with a straight copy from source to destination. What
makes this really fast is I skip copying the 'text-base' directory and
make the assumption that the source and destination are the same
revision. Essentially, only the actual changes to the WC are copied.

Several people told me that the first version was a "dangerous" tool
should someone forget to move the WC back to the PC from the laptop.
So, for the second version, I 'svn revert' the source once all
operations are done and verified. This allows the program to detect
changes to the original source when the WC is moved back to the PC.
Changes are classified into different groups. If the same file is
modified on both computers, TortoiseMerge (by default - see SVNMerge.txt
for configuring the diff/merge tool) is started up to resolve any
differences between the text-base and the files on the two computers.
All changes are saved to the source WC. Changes in this part of the
code don't actually touch the .svn directories. I make calls to 'svn
add' and 'svn delete' to let Subversion handle the necessary
modifications. After adding these modifications, several people got
quite interested and excited in the prospect of a "mobile WC".

As I said, I'm willing to share the source code. As such, I've attached
the source to this e-mail. The devs on the list will quickly tell that
this isn't the entire source code (i.e. it won't compile) but the basic
gist of its operation should be readily understandable (it is only 350
lines of code with sufficient comments - and I'm available to explain
anything). The important thing is that most of the operations are
already done by Subversion itself (revert, stat, add, delete). The only
point of contention is the direct manipulation of the .svn directories.
  It doesn't touch 'entries' at all (although I had thought about doing
that until I found 'svn stat'). Note that this is just a
proof-of-concept program - I may have missed something important but it
seems to work fine for Subversion v1.3.x for the various people using it
but I've heard that v1.4 changes the way .svn directories work, which
means SVNCopy might break a WC or the program might simply not work at
all or the changes aren't relevant and SVNCopy would continue working
fine. However, it would be best if this were an actual API/command in
Subversion itself. The concept of a "mobile WC" is what will get me out
from in front my computer indoors to my laptop outdoors. However, I see
the potential for tons of different uses for mobile WCs.

(BTW, I don't seem to be receiving mail from this list. If you send me
questions, be sure to CC me on it so that it will get to my in-box.)

--
Thomas Hruska
CubicleSoft President
Ph: 517-803-4197
Safe C++ Design Principles (First Edition)
Learn how to write memory leak-free, secure,
portable, and user-friendly software.
Learn more and view a sample chapter:
http://www.CubicleSoft.com/SafeCPPDesign/

#include "CoreLib.h"

int main(int argc, char **argv, char **envp)
{
  GxApp.Init(argc, argv, envp);

  if (GxApp.MxArgs.GetSize() < 3)
  {
    printf("Syntax: SVNCopy SrcDir DestDir\n");
    return 1;
  }

  int ReverseMode;
  BString SrcDir, DestDir, TempSrcDir, TempDestDir, TempDirFile, TempDirFile2, TempDirFile3;
  BString SVNLocation, XCopyLocation, Data, TempData;
  BString CurrLine, DirFile, Directory, Filename;
  Process TempProcess;
  Process::ProcessData TempProcessData;
  BTree<BString, int> ProcessedDirs;
  SmartPtr<int> TempNode;
  BStorage TempStorage;

  ReverseMode = 0;
  XCopyLocation = Process::FindProgram("XCopy.exe");
  SVNLocation = Process::FindProgram("svn.exe");
  SrcDir = System::AbsoluteFilename(System::CurrDirectory(), GxApp.MxArgs[1]);
  System::DirectoryToOS(SrcDir);
  TempDirFile = SrcDir + "SVNCopyMerge.dat";
  if (FS::FileExists(TempDirFile))
  {
    printf("Error: The source directory has already been SVNCopy'ed.\n");
    printf(" You must reverse the SVNCopy process before using\n");
    printf(" SVNCopy on the source directory again.\n");

    return 1;
  }
  DestDir = System::AbsoluteFilename(System::CurrDirectory(), GxApp.MxArgs[2]);
  System::DirectoryToOS(DestDir);
  TempDirFile = DestDir + "SVNCopyMerge.dat";
  if (FS::FileExists(TempDirFile))
  {
    printf("Merging any changes in the destination with the source.\n");

    // Load in the status of the destination repository.
    TempData = FS::QuoteFilename(SVNLocation) + " stat";
    TempProcessData.Extract(TempData);
    printf("Getting destination status:\n%s\n%s\n", *DestDir, *TempData);
    if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, DestDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
    {
      printf("Error running 'svn stat' on the destination directory.\n");
      return 1;
    }
    TempData = TempProcess.ReadStderr();
    if (TempData.GetSize())
    {
      printf("%s\n", *TempData);
      return 1;
    }
    Data = TempProcess.ReadStdout();

    Data.LineInput(CurrLine);
    while (CurrLine.GetSize())
    {
      // Extract path and filename information.
      CurrLine.Mid(DirFile, 7);
      System::FileToOS(DirFile);
      Directory = System::ExtractDirectory(DirFile);
      Filename = System::ExtractFilename(DirFile);
      TempSrcDir = SrcDir + Directory;
      TempDestDir = DestDir + Directory;

      switch (CurrLine[0])
      {
        case 'A':
        case 'M':
        {
          TempDirFile = TempDestDir + Filename;
          TempDirFile2 = TempSrcDir + Filename;
          if (FS::FileExists(TempDirFile))
          {
            if (FS::FileExists(TempDirFile2))
            {
              // Merge the files.
              TempData.LoadFromFile(System::AppDirectory("") + "SVNMerge.txt");
              TempData = TempData.LineInput();
              TempDirFile3 = TempSrcDir + ".svn/text-base/" + Filename + ".svn-base";
              System::FileToOS(TempDirFile3);
              TempData.ReplaceAll("%BASE", FS::QuoteFilename(TempDirFile3));
              TempData.ReplaceAll("%LEFT", FS::QuoteFilename(TempDirFile));
              TempData.ReplaceAll("%RIGHT", FS::QuoteFilename(TempDirFile2));
              TempData.ReplaceAll("%DEST", FS::QuoteFilename(TempDirFile2));
              printf("Running merge/diff utility:\n%s\n", *TempData);
              TempProcessData.Extract(TempData);
              if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, TempProcessData.Directory, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
              {
                printf("Error running merge/diff utility.\n");
                return 1;
              }
              TempData = TempProcess.ReadStderr();
              if (TempData.GetSize())
              {
                printf("%s\n", *TempData);
                return 1;
              }
              TempData = TempProcess.ReadStdout();
              if (TempData.GetSize()) printf("%s\n", *TempData);
            }
            else
            {
              // Copy the file.
              printf("Copying '%s' => '%s'...\n", *TempDirFile, *TempDirFile2);
              if (!FS::Copy(TempDirFile, TempDirFile2))
              {
                printf("Copy operation failed.\n");
                return 1;
              }

              // Add the file.
              TempData = FS::QuoteFilename(SVNLocation) + " add " + FS::QuoteFilename(DirFile);
              printf("Adding file:\n%s\n", *TempData);
              TempProcessData.Extract(TempData);
              if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, SrcDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
              {
                printf("Error running 'svn add' on the file.\n");
                return 1;
              }
              TempData = TempProcess.ReadStderr();
              if (TempData.GetSize())
              {
                printf("%s\n", *TempData);
                return 1;
              }
              TempData = TempProcess.ReadStdout();
              if (TempData.GetSize()) printf("%s\n", *TempData);
            }
          }
          else if (!FS::DirectoryExists(TempDirFile2))
          {
            // Create the directory.
            FS::MakeRecursiveDir(TempDirFile2);

            // Add the directory.
            TempData = FS::QuoteFilename(SVNLocation) + " add " + FS::QuoteFilename(DirFile);
            printf("Adding directory:\n%s\n", *TempData);
            TempProcessData.Extract(TempData);
            if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, SrcDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
            {
              printf("Error running 'svn add' on the directory.\n");
              return 1;
            }
            TempData = TempProcess.ReadStderr();
            if (TempData.GetSize())
            {
              printf("%s\n", *TempData);
              return 1;
            }
            TempData = TempProcess.ReadStdout();
            if (TempData.GetSize()) printf("%s\n", *TempData);
          }

          break;
        }
        case 'D':
        {
          TempDirFile = TempSrcDir + Filename;
          if (FS::FileExists(TempDirFile) || FS::DirectoryExists(TempDirFile))
          {
            // Delete the file or directory.
            TempData = FS::QuoteFilename(SVNLocation) + " delete " + FS::QuoteFilename(DirFile);
            printf("Removing file or directory:\n%s\n", *TempData);
            TempProcessData.Extract(TempData);
            if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, SrcDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
            {
              printf("Error running 'svn delete' on the file or directory.\n");
              return 1;
            }
            TempData = TempProcess.ReadStderr();
            if (TempData.GetSize())
            {
              printf("%s\n", *TempData);
              return 1;
            }
            TempData = TempProcess.ReadStdout();
            if (TempData.GetSize()) printf("%s\n", *TempData);
          }

          break;
        }
        case '?': break;
        default:
        {
          printf("Unknown action: %s\n", *CurrLine);
          return 1;
        }
      }

      Data.LineInput(CurrLine);
    }

    FS::Delete(DestDir + "SVNCopyMerge.dat");
    ReverseMode = 1;
  }

  // Revert the destination repository.
  TempData = FS::QuoteFilename(SVNLocation) + " revert -R -q " + FS::QuoteFilename(DestDir);
  printf("Reverting destination:\n%s\n", *TempData);
  TempProcessData.Extract(TempData);
  if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, DestDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
  {
    printf("Error running 'svn revert' on the destination directory.\n");
    return 1;
  }
  TempData = TempProcess.ReadStderr();
  if (TempData.GetSize())
  {
    printf("%s\n", *TempData);
    return 1;
  }
  TempData = TempProcess.ReadStdout();
  if (TempData.GetSize()) printf("%s\n", *TempData);

  // Load in the status of the source repository.
  TempData = FS::QuoteFilename(SVNLocation) + " stat";
  TempProcessData.Extract(TempData);
  printf("Getting source status:\n%s\n%s\n", *SrcDir, *TempData);
  if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, SrcDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
  {
    printf("Error running 'svn stat' on the source directory.\n");
    return 1;
  }
  TempData = TempProcess.ReadStderr();
  if (TempData.GetSize())
  {
    printf("%s\n", *TempData);
    return 1;
  }
  Data = TempProcess.ReadStdout();

  Data.LineInput(CurrLine);
  while (CurrLine.GetSize())
  {
    // Extract path and filename information.
    CurrLine.Mid(DirFile, 7);
    Directory = System::ExtractDirectory(DirFile);
    Filename = System::ExtractFilename(DirFile);
    TempSrcDir = SrcDir + Directory;
    TempDestDir = DestDir + Directory;

    // Check for never seen before directory.
    TempNode = ProcessedDirs.Find(Directory);
    if (!TempNode.Attached())
    {
      // Copy all files in TempSrcDir\.svn to TempDestDir\.svn
      FS::MakeRecursiveDir(TempDestDir + ".svn");
      FS::AddAttribute(TempDestDir + ".svn", FS::AttributeHidden);
      TempData = FS::QuoteFilename(XCopyLocation) + " " + FS::QuoteFilename(TempSrcDir + ".svn\\*.*") + " " + FS::QuoteFilename(TempDestDir + ".svn") + " /EXCLUDE:SVNExclude.txt /E /V /C /Q /H /R /K /Y";
      printf("Running 'xcopy' command:\n%s\n", *TempData);
      TempProcessData.Extract(TempData);
      if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, System::AppDirectory(""), BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
      {
        printf("Error running 'xcopy' command.");
        return 1;
      }
      TempData = TempProcess.ReadStderr();
      if (TempData.GetSize())
      {
        printf("%s\n", *TempData);
        return 1;
      }
      TempData = TempProcess.ReadStdout();
      if (TempData.GetSize()) printf("%s\n", *TempData);

      ProcessedDirs.Insert(Directory, 1);
    }

    switch (CurrLine[0])
    {
      case 'A':
      case 'M':
      {
        // Copy the file.
        TempDirFile = TempSrcDir + Filename;
        TempDirFile2 = TempDestDir + Filename;
        printf("Copying '%s' => '%s'...\n", *TempDirFile, *TempDirFile2);
        if (!FS::Copy(TempDirFile, TempDirFile2))
        {
          printf("Copy operation failed.\n");
          return 1;
        }

        break;
      }
      case 'D':
      {
        // Delete the file.
        TempDirFile = TempDestDir + Filename;
        printf("Deleting '%s'...\n", *TempDirFile);
        if (FS::FileExists(TempDirFile) && !FS::Delete(TempDirFile))
        {
          printf("Deletion failed.\n");
          return 1;
        }

        break;
      }
      case '?': break;
      default:
      {
        printf("Unknown action: %s\n", *CurrLine);
        return 1;
      }
    }

    Data.LineInput(CurrLine);
  }

  // Revert the source repository.
  TempData = FS::QuoteFilename(SVNLocation) + " revert -R -q " + FS::QuoteFilename(SrcDir);
  printf("Reverting source:\n%s\n", *TempData);
  TempProcessData.Extract(TempData);
  if (!TempProcess.StartProcess(TempProcessData.Command, TempProcessData.Argv, TempProcessData.Envp, SrcDir, BString(), Process::RunType::Foreground, Process::OutputType::Piped, Process::OutputType::Piped))
  {
    printf("Error running 'svn revert' on the source directory.\n");
    return 1;
  }
  TempData = TempProcess.ReadStderr();
  if (TempData.GetSize())
  {
    printf("%s\n", *TempData);
    return 1;
  }
  TempData = TempProcess.ReadStdout();
  if (TempData.GetSize()) printf("%s\n", *TempData);

  // Create the SVNCopyMerge.dat file.
  if (!ReverseMode)
  {
    Data.Empty();
    Data.SaveAsFile(SrcDir + "SVNCopyMerge.dat");
  }

  return 0;
}

---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Fri Jul 21 20:48:58 2006

This is an archived mail posted to the Subversion Dev mailing list.