1 // GetCommand.cpp: implementation of the GetCommand class.
3 //////////////////////////////////////////////////////////////////////
6 #include "GetCommand.h"
7 #include <SSPhysLib/SSFiles.h>
8 #include <SSPhysLib/SSItemInfoObject.h>
9 #include <SSPhysLib/SSVersionObject.h>
10 #include <SSPhysLib/SSProjectObject.h>
11 #include <boost/integer/static_min_max.hpp>
12 #include <boost/filesystem/operations.hpp>
13 #include <boost/filesystem/convenience.hpp> // create_directories
14 #include <boost/filesystem/exception.hpp>
20 //using namespace boost::filesystem;
21 namespace fs
= boost::filesystem
;
23 //////////////////////////////////////////////////////////////////////
24 // Construction/Destruction
25 //////////////////////////////////////////////////////////////////////
27 CGetCommand::CGetCommand ()
28 : CCommand ("get", "Retrieve old versions from a VSS physical file"),
30 m_bForceOverwrite (false),
35 po::options_description
CGetCommand::GetOptionsDescription () const
37 po::options_description
descr (CCommand::GetOptionsDescription());
39 ("version,v", po::value
<int>(), "Get a specific old version")
40 ("force-overwrite", "overwrite target file")
41 ("bulk,b", "bulk operation: get all intermediate files with the same name as the source name")
42 ("projects,p", "Experimental: also try to rebuild project files")
43 ("input", po::value
<std::string
>(), "input physical file name")
44 ("output", po::value
<std::string
>(), "target file name.\n"
46 "You can also specify an oputput directory for the output target."
47 "In that case the name of the input file will be used "
48 "as the output file name. With this option, you can easily "
49 "build a shadow directory of your data, e.g. with the following command\n"
51 " find data -name ???????? | xargs -n 1 ssphys get -b -v 1 -s 1 --output shadowdir/ \n"
53 "If you specify a relative or absolute path to the physical file, all non "
54 "directory elements will be appended to the output directory, e.g.\n"
56 " ssphys get data/b/baaaaaaa shadow/ \n"
58 "will output all files to \"shadow/data/b/baaaaaaa\". You can control the number "
59 "of directories appended with the --strip option" )
60 ("strip", po::value
<int> (), "Strip the smallest prefix containing num leading slashes from the input path "
61 "A sequence of one or more adjacent slashes is counted as a single slash, e.g\n"
63 "/path/to/soursafe/archive/data/a/abaaaaaa\n"
65 "setting --strip 0 gives the entire file name unmodified, --strip 1 gives\n"
67 "path/to/soursafe/archive/data/a/abaaaaaa\n"
69 "without the leading slash, --strip 6 gives\n"
73 "and not specifying --strip at all just gives you abaaaaaa.")
78 po::options_description
CGetCommand::GetHiddenDescription () const
80 po::options_description
descr (CCommand::GetHiddenDescription());
84 po::positional_options_description
CGetCommand::GetPositionalOptionsDescription () const
86 po::positional_options_description
positional (CCommand::GetPositionalOptionsDescription());
87 positional
.add ("input", 1);
88 positional
.add ("output", 2);
92 class CActionVisitor
: public ISSActionVisitor
95 virtual ~CActionVisitor () {}
97 virtual bool Apply (const SSLabeledAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
98 // virtual bool Apply (const SSSingleFileAction& rAction) { return Apply ((const SSAction&) rAction); }
99 virtual bool Apply (const SSCreatedProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
100 virtual bool Apply (const SSCreatedFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
101 virtual bool Apply (const SSAddedProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
102 virtual bool Apply (const SSAddedFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
103 virtual bool Apply (const SSDeletedProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
104 virtual bool Apply (const SSDeletedFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
105 virtual bool Apply (const SSRecoveredProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
106 virtual bool Apply (const SSRecoveredFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
107 virtual bool Apply (const SSBranchFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
108 virtual bool Apply (const SSRollbackAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
109 virtual bool Apply (const SSArchivedVersionsAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
110 virtual bool Apply (const SSArchiveFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
111 virtual bool Apply (const SSArchiveProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
112 virtual bool Apply (const SSRestoreFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
113 virtual bool Apply (const SSRestoreProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
115 virtual bool Apply (const SSDestroyedProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
116 virtual bool Apply (const SSDestroyedFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
117 virtual bool Apply (const SSRenamedProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
118 virtual bool Apply (const SSRenamedFileAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
119 virtual bool Apply (const SSCheckedInAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
120 virtual bool Apply (const SSSharedAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
122 virtual bool Apply (const SSMovedProjectAction
& rAction
) { return Apply ((const SSAction
&) rAction
); }
125 virtual bool Apply (const SSAction
& rAction
)
134 CAutoFile (std::string name
, bool bDelete
= true)
136 m_bDeleteFile (bDelete
)
140 CAutoFile (CAutoFile
const & r
)
141 : m_bDeleteFile (r
.m_bDeleteFile
),
142 m_FileName (r
.Detatch ())
146 CAutoFile
& operator= (CAutoFile
const & r
)
150 if (m_FileName
!= r
.GetPath())
154 m_bDeleteFile
= r
.m_bDeleteFile
;
156 else if (r
.m_bDeleteFile
)
157 m_bDeleteFile
= r
.m_bDeleteFile
;
158 m_FileName
= r
.Detatch();
169 std::string
GetPath () const { return m_FileName
; }
171 std::string
Detatch() const;
176 std::string m_FileName
;
177 mutable bool m_bDeleteFile
;
180 std::string
CAutoFile::Detatch() const
182 m_bDeleteFile
= false;
186 int CAutoFile::Unlink()
190 m_bDeleteFile
= false;
191 return( unlink( m_FileName
.c_str () ) );
204 CReverseDelta (const char* buffer
, size_t length
)
205 : m_pBuffer (buffer
), m_length (length
)
209 bool operator () (std::istream
& input
, std::ostream
& output
) const
211 for (size_t i
= 0; i
< m_length
; )
213 const FD
* pfd
= (const FD
*) (m_pBuffer
+i
);
215 // printf ("fd: %d, start %d, len %d\n", pfd->command, pfd->start, pfd->end);
217 switch (pfd
->command
)
225 long size
= pfd
->end
;
226 input
.seekg(pfd
->start
);
228 throw SSException ("reverse delta: invalid seek beyond file size");
230 while (size
> 0 && !input
.fail () && !output
.fail ())
232 long s
= std::min (size
, (long) sizeof (b
));
236 // how many bytes did we really read?
242 throw SSException ("reverse delta: failed to read necessary amount of data from input file");
244 throw SSException ("reverse delta: failed to write necessary amount of data to the output file");
249 long s
= std::min (pfd
->end
, (ulong
)(m_length
- i
));
250 output
.write (m_pBuffer
+i
, s
);
253 throw SSException ("reverse delta: invalid patch length in delta record");
260 msg
<< "unknown reverse delta command " << pfd
->command
;
261 throw SSException (msg
.str());
269 const char* m_pBuffer
;
274 class CHistoryHandler
: public CActionVisitor
277 CHistoryHandler (std::string src
)
278 : m_File (src
, false)
282 std::string
GetPath ()
284 return m_File
.GetPath();
287 virtual void SaveAs (std::string name
, bool overwrite
= false) = 0;
294 class CReverseHistoryHandler
: public CHistoryHandler
297 CReverseHistoryHandler (std::string src
)
298 : CHistoryHandler (src
)
302 virtual bool Apply (const SSCreatedFileAction
& rAction
)
304 // create an empty file
305 CAutoFile
targetFile (tmpnam(NULL
));
311 virtual bool Apply (const SSArchivedVersionsAction
& rAction
)
313 // end building the history at this point
317 virtual bool Apply (const SSLabeledAction
& rAction
)
319 // nothing to do for a file labeling operation
323 virtual bool Apply (const SSRollbackAction
& rAction
)
325 // nothing special to do for a rollback operation
326 // The rollback action just marks the begin of this file
330 virtual bool Apply (const SSCheckedInAction
& rAction
)
332 SSRecordPtr pRecord
= rAction
.GetFileDelta();
335 throw SSException ("no file delta record found for check-in action (probably item did not retained old versions of itself)");
338 CAutoFile
targetFile (tmpnam(NULL
));
340 std::ifstream
input (m_File
.GetPath().c_str(), std::ios::in
|std::ios::binary
);
341 if (!input
.is_open())
343 std::ofstream
output (targetFile
.GetPath().c_str(), std::ios::out
|std::ios::binary
);
344 if (!output
.is_open())
347 CReverseDelta
revDelta ((const char*)pRecord
->GetBuffer(), pRecord
->GetLen());
348 bool ret
= revDelta (input
, output
);
360 virtual void SaveAs (std::string name
, bool overwrite
= false)
362 fs::path
fpath (name
);
363 if (overwrite
&& fs::exists (fpath
))
365 else if (!fs::exists (fpath
.branch_path ()))
366 fs::create_directories(fpath
.branch_path ());
368 fs::copy_file (GetPath (), fpath
);
371 virtual bool Apply (const SSAction
& rAction
)
373 throw SSException (std::string ("unsuported action in CReverseHistoryHandler: ").append (CAction::ActionToString(rAction
.GetActionID())));
378 class CProjectHistoryHandler
: public CHistoryHandler
381 CProjectHistoryHandler (std::string src
)
382 : CHistoryHandler (src
)
384 SSProjectFile
projectFile (GetPath ());
385 BuildList (projectFile
);
388 virtual void SaveAs (std::string name
, bool overwrite
= false)
390 boost::throw_exception (std::logic_error ("get not yet implemented for project status files"));
393 virtual bool Apply (const SSLabeledAction
& rAction
) { /* nothing to do */ return true; }
394 //// virtual bool Apply (const SSSingleFileAction& rAction) { return Apply ((const SSAction&) rAction); }
395 virtual bool Apply (const SSCreatedProjectAction
& rAction
) { /* nothing to do */ return true; }
396 virtual bool Apply (const SSCreatedFileAction
& rAction
) { /* nothing to do */ return true; }
397 virtual bool Apply (const SSAddedProjectAction
& rAction
);
398 virtual bool Apply (const SSAddedFileAction
& rAction
);
399 virtual bool Apply (const SSDeletedProjectAction
& rAction
);
400 virtual bool Apply (const SSDeletedFileAction
& rAction
);
401 virtual bool Apply (const SSRecoveredProjectAction
& rAction
);
402 virtual bool Apply (const SSRecoveredFileAction
& rAction
);
404 virtual bool Apply (const SSDestroyedProjectAction
& rAction
);
405 virtual bool Apply (const SSDestroyedFileAction
& rAction
);
406 virtual bool Apply (const SSRenamedProjectAction
& rAction
);
407 virtual bool Apply (const SSRenamedFileAction
& rAction
);
408 // virtual bool Apply (const SSCheckedInAction& rAction) { return Apply ((const SSAction&) rAction); }
410 // virtual bool Apply (const SSSharedFileAction& rAction) { return Apply ((const SSAction&) rAction); }
411 // virtual bool Apply (const SSMovedProjectAction& rAction);
414 virtual bool Apply (const SSAction
& rAction
)
416 throw SSException ("unsuported action in CProjectHistoryHandler");
419 typedef std::vector
<SSProjectObject
>::iterator iterator
;
420 std::vector
<SSProjectObject
> m_Items
;
422 void BuildList (SSProjectFile
& rFile
);
423 iterator
FindItem (std::string physFile
);
424 iterator
InsertItem (SSProjectObject object
);
429 void CProjectHistoryHandler::BuildList (SSProjectFile
& rFile
)
431 // iterate all records and add them to the collection
432 SSRecordPtr recordPtr
= rFile
.GetFirstRecord ();
435 if (recordPtr
->GetType() == eProjectEntry
)
437 SSProjectObject
projectObject (recordPtr
);
438 m_Items
.push_back (projectObject
);
441 recordPtr
= rFile
.GetNextRecord (recordPtr
);
445 CProjectHistoryHandler::iterator
CProjectHistoryHandler::FindItem (std::string physFile
)
448 iterator end
= m_Items
.end();
449 iterator found
= end
;
451 for (itor
= m_Items
.begin(); itor
!= end
; ++itor
)
453 SSProjectObject
& po
= *itor
;
454 // std::cout << itemPtr->GetPhysical () << std::endl;
455 if (physFile
== po
.GetPhysFile())
458 throw SSException ("duplicate entry");
467 CProjectHistoryHandler::iterator
CProjectHistoryHandler::InsertItem (SSProjectObject object
)
469 m_Items
.push_back (object
);
470 iterator last
= m_Items
.end();
474 bool CProjectHistoryHandler::Apply (const SSAddedFileAction
& rAction
)
476 iterator itor
= FindItem (rAction
.GetPhysical());
477 if (itor
!= m_Items
.end())
480 throw SSException ("item not found");
485 bool CProjectHistoryHandler::Apply (const SSAddedProjectAction
& rAction
)
487 iterator itor
= FindItem (rAction
.GetPhysical());
488 if (itor
!= m_Items
.end())
491 throw SSException ("item not found");
496 bool CProjectHistoryHandler::Apply (const SSDeletedFileAction
& rAction
)
498 iterator itor
= FindItem (rAction
.GetPhysical());
499 if (itor
!= m_Items
.end ())
501 SSProjectObject
& po
= *itor
;
505 throw SSException ("item not found");
509 bool CProjectHistoryHandler::Apply (const SSDeletedProjectAction
& rAction
)
511 iterator itor
= FindItem (rAction
.GetPhysical());
512 if (itor
!= m_Items
.end ())
514 SSProjectObject
& po
= *itor
;
518 throw SSException ("item not found");
522 bool CProjectHistoryHandler::Apply (const SSRecoveredFileAction
& rAction
)
524 iterator itor
= FindItem (rAction
.GetPhysical());
525 if (itor
!= m_Items
.end ())
527 SSProjectObject
& po
= *itor
;
531 throw SSException ("item not found");
535 bool CProjectHistoryHandler::Apply (const SSRecoveredProjectAction
& rAction
)
537 iterator itor
= FindItem (rAction
.GetPhysical());
538 if (itor
!= m_Items
.end ())
540 SSProjectObject
& po
= *itor
;
544 throw SSException ("item not found");
549 bool CProjectHistoryHandler::Apply (const SSDestroyedProjectAction
& rAction
)
551 if (FindItem (rAction
.GetPhysical ()) != m_Items
.end ())
552 throw SSException ("adding already existing item");
556 pe
.name
= rAction
.GetSSName ();
557 strncpy (pe
.phys
, rAction
.GetPhysical().c_str(), 8);
559 pe
.pinnedToVersion
= 0;
561 SSProjectObject
pr (pe
);
567 bool CProjectHistoryHandler::Apply (const SSDestroyedFileAction
& rAction
)
569 // durch map wahrscheinlich besser zu lösen
570 if (FindItem (rAction
.GetPhysical()) != m_Items
.end ())
571 throw SSException ("adding already existing item");
575 pe
.name
= rAction
.GetSSName ();
576 strncpy (pe
.phys
, rAction
.GetPhysical().c_str(), 8);
578 pe
.pinnedToVersion
= 0;
580 SSProjectObject
pr (pe
);
586 bool CProjectHistoryHandler::Apply (const SSRenamedProjectAction
& rAction
)
588 iterator itor
= FindItem (rAction
.GetPhysical());
589 if (itor
== m_Items
.end ())
590 throw SSException ("item not found");
592 SSProjectObject
& po
= *itor
;
593 po
.Rename (rAction
.GetNewSSName (), rAction
.GetSSName());
597 bool CProjectHistoryHandler::Apply (const SSRenamedFileAction
& rAction
)
599 iterator itor
= FindItem (rAction
.GetPhysical());
600 if (itor
== m_Items
.end ())
601 throw SSException ("item not found");
603 SSProjectObject
& po
= *itor
;
604 po
.Rename (rAction
.GetNewSSName (), rAction
.GetSSName());
613 void CGetCommand::Execute (po::variables_map
const & options
, std::vector
<po::option
> const & args
)
615 std::string physFile
;
616 std::string destFile
;
620 fs::path::default_name_check (fs::native
);
622 if (options
.count("version"))
623 m_Version
= options
["version"].as
<int>();
624 if (options
.count("force-overwrite"))
625 m_bForceOverwrite
= true;
626 if (options
.count("bulk"))
629 if (options
.count("input"))
630 physPath
= physFile
= options
["input"].as
<std::string
>();
631 if (options
.count("output"))
632 destPath
= destFile
= options
["output"].as
<std::string
> ();
636 if (physFile
.empty ())
637 throw SSException ("please specify a source file for the get operation");
639 if (destFile
.empty ())
640 throw SSException ("please specify a destination file for the get operation");
642 if (fs::exists (destPath
) && fs::is_directory(destPath
) || *destFile
.rbegin() == '\\' || *destFile
.rbegin() == '/')
644 fs::path::iterator itor
= physPath
.begin ();
645 int strip
= options
.count ("strip") ? options
["strip"].as
<int> () : 0;
647 while (strip
> 0 && itor
!= physPath
.end ())
654 if (itor
== physPath
.end ())
655 relPath
= physPath
.leaf ();
658 while (itor
!= physPath
.end ())
665 destPath
= destFile
/ relPath
;
668 if (fs::exists (destPath
) && !m_bForceOverwrite
)
669 throw SSException ("destination file exists. Please use overwrite flag");
672 SSHistoryFile
file(physPath
.string ());
673 std::auto_ptr
<SSItemInfoObject
> pItem (file
.GetItemInfo());
675 throw SSException ("no information object found");
677 std::auto_ptr
<CHistoryHandler
> pVisitor
;
678 std::string lastDataFileName
= pItem
->GetDataFileName ();
680 if (pItem
->GetType() == SSITEM_FILE
)
681 pVisitor
= std::auto_ptr
<CHistoryHandler
> (new CReverseHistoryHandler (lastDataFileName
));
682 else if (options
.count("projects"))
683 pVisitor
= std::auto_ptr
<CHistoryHandler
> (new CProjectHistoryHandler (lastDataFileName
));
688 m_Version
= pItem
->GetNumberOfActions () - m_Version
+ 1;
690 SSFileItem
* pFileItem
= dynamic_cast<SSFileItem
*> (pItem
.get());
692 // This check isn't necessary, since the get code will fail, if not delta information is available
693 // The code was only here to give a better error message. But in case, that the flag was added after
694 // the last commit, it is possible, that parts of the file are still recoverable. Therefore I disabled
697 if (pFileItem && pFileItem->GetStoreOnlyLatestRev ())
699 if (m_Version < pItem->GetNumberOfActions ())
701 throw SSException ("item does not retain old versions of itself");
706 // we need to initialize the tmpnam once. The first file generated by tmpnam has a trailing dot, but no
707 // extension, e.g "sesc.". And this is an invalid name for the boost::filesystem library
708 CAutoFile
tmpfile (tmpnam(NULL
));
711 SSVersionObject
version (pItem
->GetHistoryLast ());
712 while (version
&& version
.GetVersionNumber() > m_Version
)
714 if (version
.GetAction())
718 std::string
bulkFile (destPath
.string () + "." + boost::lexical_cast
<std::string
>(version
.GetVersionNumber ()));
720 pVisitor
->SaveAs (bulkFile
, m_bForceOverwrite
);
721 // throw SSException ("failed to create target file " + bulkFile);
724 if (!version
.GetAction ()->Accept (*pVisitor
.get()))
728 version
= version
.GetPreviousObject ();
732 destPath
= destPath
.string () + "." + boost::lexical_cast
<std::string
>(version
? version
.GetVersionNumber () : 0);
734 pVisitor
->SaveAs (destPath
.string (), m_bForceOverwrite
);
735 // throw SSException (std::string ("failed to create target file ").append (m_DestFile));