20 using namespace swissknife;
22 const std::string CommandTag::kHeadTag =
"trunk";
23 const std::string CommandTag::kPreviousHeadTag =
"trunk-previous";
25 const std::string CommandTag::kHeadTagDescription =
"current HEAD";
26 const std::string CommandTag::kPreviousHeadTagDescription =
27 "default undo target";
30 r->push_back(Parameter::Mandatory(
'w',
"repository directory / url"));
31 r->push_back(Parameter::Mandatory(
't',
"temporary scratch directory"));
32 r->push_back(Parameter::Optional(
'p',
"public key of the repository"));
33 r->push_back(Parameter::Optional(
'f',
"fully qualified repository name"));
34 r->push_back(Parameter::Optional(
'r',
"spooler definition string"));
35 r->push_back(Parameter::Optional(
'm',
"(unsigned) manifest file to edit"));
36 r->push_back(Parameter::Optional(
'b',
"mounted repository base hash"));
38 Parameter::Optional(
'e',
"hash algorithm to use (default SHA1)"));
39 r->push_back(Parameter::Switch(
'L',
"follow HTTP redirects"));
40 r->push_back(Parameter::Optional(
'P',
"session_token_file"));
41 r->push_back(Parameter::Optional(
'@',
"proxy url"));
48 const string spl_definition =
49 (args.find(
'r') == args.end())
52 const string manifest_path = (args.find(
'm') == args.end())
56 (args.find(
'e') == args.end())
59 const string pubkey_path = (args.find(
'p') == args.end())
63 (args.find(
'b') == args.end())
67 const string repo_name =
68 (args.find(
'f') == args.end()) ?
"" : *args.find(
'f')->second;
70 string session_token_file;
71 if (args.find(
'P') != args.end()) {
72 session_token_file = *args.find(
'P')->second;
81 if (read_write && spl_definition.empty()) {
86 if (read_write && manifest_path.empty()) {
91 if (!read_write && pubkey_path.empty()) {
96 if (!read_write && repo_name.empty()) {
101 if (
HasPrefix(spl_definition,
"gw",
false)) {
102 if (session_token_file.empty()) {
104 "Session token file has to be provided "
105 "when upstream type is gw.");
115 env->manifest_path.Set(manifest_path);
116 env->history_path.Set(
CreateTempPath(tmp_path +
"/history", 0600));
119 const bool follow_redirects = (args.count(
'L') > 0);
120 const std::string &proxy = (args.count(
'@') > 0) ?
121 *args.find(
'@')->second :
"";
122 if (!this->InitDownloadManager(follow_redirects, proxy)) {
127 if (!pubkey_path.empty() && !this->InitSignatureManager(pubkey_path)) {
135 ? OpenLocalManifest(env->manifest_path.path())
136 : FetchRemoteManifest(env->repository_url, repo_name, base_hash);
138 if (!env->manifest.
IsValid()) {
144 if (read_write && env->manifest->history().IsNull() && !base_hash.
IsNull()) {
145 env->previous_manifest =
146 FetchRemoteManifest(env->repository_url, repo_name, base_hash);
147 if (!env->previous_manifest.
IsValid()) {
153 "using history database '%s' from previous "
154 "manifest (%s) as basis",
155 env->previous_manifest->history().ToString().c_str(),
156 env->previous_manifest->repository_name().c_str());
157 env->manifest->set_history(env->previous_manifest->history());
158 env->manifest->set_repository_name(
159 env->previous_manifest->repository_name());
163 env->history = GetHistory(env->manifest.
weak_ref(), env->repository_url,
164 env->history_path.path(), read_write);
173 const bool use_file_chunking =
false;
174 const bool generate_legacy_bulk_chunks =
false;
177 generate_legacy_bulk_chunks,
178 use_file_chunking, 0, 0, 0,
180 env->spooler = upload::Spooler::Construct(sd);
203 upload::Spooler::CallbackPtr callback = env->
spooler->RegisterListener(
204 &CommandTag::UploadClosure,
this, &history_hash);
208 env->
spooler->UnregisterListener(callback);
211 if (new_history_hash.
IsNull()) {
216 env->
manifest->set_history(new_history_hash);
226 "exported manifest (%" PRIu64
") with new history '%s'",
233 bool CommandTag::UploadCatalogAndUpdateManifest(
239 const std::string catalog_path = wr_catalog->database_path();
240 env->
manifest->set_ttl(wr_catalog->GetTTL());
241 env->
manifest->set_revision(wr_catalog->GetRevision());
242 env->
manifest->set_publish_timestamp(wr_catalog->GetLastModified());
250 upload::Spooler::CallbackPtr callback = env->
spooler->RegisterListener(
251 &CommandTag::UploadClosure,
this, &catalog_hash);
252 env->
spooler->ProcessCatalog(catalog_path);
255 env->
spooler->UnregisterListener(callback);
258 if (new_catalog_hash.
IsNull()) {
260 catalog_path.c_str());
265 const size_t catalog_size =
GetFileSize(catalog_path);
266 env->
manifest->set_catalog_size(catalog_size);
267 env->
manifest->set_catalog_hash(new_catalog_hash);
270 catalog_size, new_catalog_hash.
ToString().c_str());
287 bool CommandTag::UpdateUndoTags(
289 const bool undo_rollback) {
296 if (!env->
history->Remove(CommandTag::kPreviousHeadTag)) {
302 if (env->
history->GetByName(CommandTag::kHeadTag, ¤t_head)) {
304 if (!env->
history->Remove(CommandTag::kHeadTag)) {
310 if (!undo_rollback) {
311 current_old_head = current_head;
312 current_old_head.
name = CommandTag::kPreviousHeadTag;
313 current_old_head.
description = CommandTag::kPreviousHeadTagDescription;
314 if (!env->
history->Insert(current_old_head)) {
322 current_head = current_head_template;
323 current_head.
name = CommandTag::kHeadTag;
324 current_head.description = CommandTag::kHeadTagDescription;
325 if (!env->
history->Insert(current_head)) {
333 bool CommandTag::FetchObject(
const std::string &repository_url,
335 const std::string &destination_path)
const {
339 const std::string url = repository_url +
"/data/" + object_hash.
MakePath();
343 dl_retval = download_manager()->Fetch(&download_object);
356 const std::string &repository_url,
357 const std::string &history_path,
358 const bool read_write)
const {
362 if (history_hash.
IsNull()) {
365 if (NULL == history) {
370 if (!FetchObject(repository_url, history_hash, history_path)) {
376 if (NULL == history) {
378 history_path.c_str());
379 unlink(history_path.c_str());
391 const std::string catalog_path,
392 const bool read_write)
const {
394 if (!FetchObject(repository_url, catalog_hash, catalog_path)) {
398 const std::string catalog_root_path =
"";
400 catalog_root_path, catalog_path, catalog_hash)
402 catalog_root_path, catalog_path, catalog_hash);
405 void CommandTag::PrintTagMachineReadable(
415 std::string CommandTag::AddPadding(
const std::string &str,
const size_t padding,
416 const bool align_right,
417 const std::string &fill_char)
const {
418 assert(str.size() <= padding);
419 std::string result(str);
420 result.resize(padding);
421 const size_t pos = (align_right) ? 0 : str.size();
422 const size_t padding_width = padding - str.size();
423 for (
size_t i = 0; i < padding_width; ++i) result.insert(pos, fill_char);
427 bool CommandTag::IsUndoTagName(
const std::string &tag_name)
const {
428 return tag_name == CommandTag::kHeadTag ||
429 tag_name == CommandTag::kPreviousHeadTag;
438 r.push_back(Parameter::Optional(
'd',
"space separated tags to be deleted"));
439 r.push_back(Parameter::Optional(
'a',
"name of the new tag"));
440 r.push_back(Parameter::Optional(
'D',
"description of the tag"));
441 r.push_back(Parameter::Optional(
'B',
"branch of the new tag"));
442 r.push_back(Parameter::Optional(
'P',
"predecessor branch"));
443 r.push_back(Parameter::Optional(
'h',
"root hash of the new tag"));
444 r.push_back(Parameter::Switch(
'x',
"maintain undo tags"));
449 if ((args.find(
'd') == args.end()) && (args.find(
'a') == args.end()) &&
450 (args.find(
'x') == args.end())) {
456 const bool history_read_write =
true;
464 if (args.find(
'd') != args.end()) {
465 retval = RemoveTags(args, env.
weak_ref());
466 if (retval != 0)
return retval;
468 if ((args.find(
'a') != args.end()) || (args.find(
'x') != args.end())) {
469 retval = AddNewTag(args, env.
weak_ref());
470 if (retval != 0)
return retval;
474 if (!CloseAndPublishHistory(env.
weak_ref())) {
481 const std::string tag_name =
482 (args.find(
'a') != args.end()) ? *args.find(
'a')->second :
"";
483 const std::string tag_description =
484 (args.find(
'D') != args.end()) ? *args.find(
'D')->second :
"";
485 const bool undo_tags = (args.find(
'x') != args.end());
486 const std::string root_hash_string =
487 (args.find(
'h') != args.end()) ? *args.find(
'h')->second :
"";
488 const std::string branch_name =
489 (args.find(
'B') != args.end()) ? *args.find(
'B')->second :
"";
490 const std::string previous_branch_name =
491 (args.find(
'P') != args.end()) ? *args.find(
'P')->second :
"";
493 if (tag_name.find(
" ") != std::string::npos) {
498 assert(!tag_name.empty() || undo_tags);
500 if (IsUndoTagName(tag_name)) {
507 shash::Any root_hash = GetTagRootHash(env, root_hash_string);
515 const bool catalog_read_write =
false;
527 "cannot tag catalog '%s' that is not a "
535 tag_template.
name =
"<template>";
540 tag_template.
branch = branch_name;
544 if (!tag_name.empty()) {
545 tag_template.
name = tag_name;
546 const bool user_provided_hash = (!root_hash_string.empty());
551 previous_branch_name,
553 if (!env->
history->InsertBranch(branch)) {
555 tag_template.
branch.c_str());
560 if (!ManipulateTag(env, tag_template, user_provided_hash)) {
566 if (undo_tags && !UpdateUndoTags(env, tag_template)) {
574 Environment *env,
const std::string &root_hash_string)
const {
577 if (root_hash_string.empty()) {
579 "no catalog hash provided, using hash"
580 "of current HEAD catalog (%s)",
581 env->
manifest->catalog_hash().ToString().c_str());
582 root_hash = env->
manifest->catalog_hash();
588 "failed to read provided catalog hash '%s'",
589 root_hash_string.c_str());
598 const bool user_provided_hash) {
599 const std::string &tag_name = tag_template.
name;
602 if (!env->
history->Exists(tag_name)) {
603 return CreateTag(env, tag_template);
607 if (!user_provided_hash) {
609 "a tag with the name '%s' already exists. Do you want to move it? "
616 return MoveTag(env, tag_template);
622 const std::string &tag_name = tag_template.
name;
627 if (!env->
history->GetByName(tag_name, &old_tag)) {
647 if (!env->
history->Remove(tag_name)) {
652 if (!env->
history->PruneBranches()) {
656 bool retval = env->
history->Vacuum();
664 return CreateTag(env, new_tag);
669 if (!env->
history->Insert(new_tag)) {
671 new_tag.
name.c_str());
679 typedef std::vector<std::string> TagNames;
680 const std::string tags_to_delete = *args.find(
'd')->second;
682 const TagNames condemned_tags =
SplitString(tags_to_delete,
' ');
685 TagNames::const_iterator i = condemned_tags.begin();
686 const TagNames::const_iterator iend = condemned_tags.end();
687 for (; i != iend; ++i) {
688 if (IsUndoTagName(*i)) {
690 "undo tags are handled internally and cannot be deleted");
696 condemned_tags.size());
699 bool all_exist =
true;
700 for (i = condemned_tags.begin(); i != iend; ++i) {
701 if (!env->
history->Exists(*i)) {
711 i = condemned_tags.begin();
712 env->
history->BeginTransaction();
713 for (; i != iend; ++i) {
716 const bool found_tag = env->
history->GetByName(*i, &condemned_tag);
719 condemned_tag.
name.c_str(),
723 if (!env->
history->Remove(*i)) {
729 bool retval = env->
history->PruneBranches();
732 "failed to prune unused branches from history");
735 env->
history->CommitTransaction();
736 retval = env->
history->Vacuum();
748 r.push_back(Parameter::Switch(
'x',
"machine readable output"));
749 r.push_back(Parameter::Switch(
'B',
"print branch hierarchy"));
753 void CommandListTags::PrintHumanReadableTagList(
756 const std::string name_label =
"Name";
757 const std::string rev_label =
"Revision";
758 const std::string time_label =
"Timestamp";
759 const std::string branch_label =
"Branch";
760 const std::string desc_label =
"Description";
763 TagList::const_reverse_iterator i = tags.rbegin();
764 const TagList::const_reverse_iterator iend = tags.rend();
765 size_t max_name_len = name_label.size();
766 size_t max_rev_len = rev_label.size();
767 size_t max_time_len = desc_label.size();
768 size_t max_branch_len = branch_label.size();
769 for (; i != iend; ++i) {
770 max_name_len = std::max(max_name_len, i->name.size());
771 max_rev_len = std::max(max_rev_len,
StringifyInt(i->revision).size());
773 std::max(max_time_len,
StringifyTime(i->timestamp,
true).size());
774 max_branch_len = std::max(max_branch_len, i->branch.size());
779 "%s \u2502 %s \u2502 %s \u2502 %s \u2502 %s",
780 AddPadding(name_label, max_name_len).c_str(),
781 AddPadding(rev_label, max_rev_len).c_str(),
782 AddPadding(time_label, max_time_len).c_str(),
783 AddPadding(branch_label, max_branch_len).c_str(),
786 "%s\u2500\u253C\u2500%s\u2500\u253C\u2500%s"
787 "\u2500\u253C\u2500%s\u2500\u253C\u2500%s",
788 AddPadding(
"", max_name_len,
false,
"\u2500").c_str(),
789 AddPadding(
"", max_rev_len,
false,
"\u2500").c_str(),
790 AddPadding(
"", max_time_len,
false,
"\u2500").c_str(),
791 AddPadding(
"", max_branch_len,
false,
"\u2500").c_str(),
792 AddPadding(
"", desc_label.size() + 1,
false,
"\u2500").c_str());
796 for (; i != iend; ++i) {
799 "%s \u2502 %s \u2502 %s \u2502 %s \u2502 %s",
800 AddPadding(i->name, max_name_len).c_str(),
801 AddPadding(
StringifyInt(i->revision), max_rev_len,
true).c_str(),
802 AddPadding(
StringifyTime(i->timestamp,
true), max_time_len).c_str(),
803 AddPadding(i->branch, max_branch_len).c_str(),
804 i->description.c_str());
809 "%s\u2500\u2534\u2500%s\u2500\u2534\u2500%s"
810 "\u2500\u2534\u2500%s\u2500\u2534\u2500%s",
811 AddPadding(
"", max_name_len,
false,
"\u2500").c_str(),
812 AddPadding(
"", max_rev_len,
false,
"\u2500").c_str(),
813 AddPadding(
"", max_time_len,
false,
"\u2500").c_str(),
814 AddPadding(
"", max_branch_len,
false,
"\u2500").c_str(),
815 AddPadding(
"", desc_label.size() + 1,
false,
"\u2500").c_str());
821 void CommandListTags::PrintMachineReadableTagList(
const TagList &tags)
const {
822 TagList::const_iterator i = tags.begin();
823 const TagList::const_iterator iend = tags.end();
824 for (; i != iend; ++i) {
825 PrintTagMachineReadable(*i);
830 void CommandListTags::PrintHumanReadableBranchList(
833 unsigned N = branches.size();
834 for (
unsigned i = 0; i < N; ++i) {
835 for (
unsigned l = 0; l < branches[i].level; ++l) {
837 ((l + 1) == branches[i].level) ?
"\u251c " :
"\u2502 ");
840 branches[i].branch.branch.c_str(),
841 branches[i].branch.initial_revision);
846 void CommandListTags::PrintMachineReadableBranchList(
849 unsigned N = branches.size();
850 for (
unsigned i = 0; i < N; ++i) {
853 AddPadding(
"", branches[i].level,
false,
" ").c_str(),
854 branches[i].branch.branch.c_str(),
855 branches[i].branch.initial_revision);
860 void CommandListTags::SortBranchesRecursively(
862 const string &parent_branch,
868 unsigned N = branches.size();
869 for (
unsigned i = 0; i < N; ++i) {
870 if (branches[i].branch ==
"")
872 if (branches[i].parent == parent_branch) {
873 hierarchy->push_back(
BranchLevel(branches[i], level));
874 SortBranchesRecursively(
875 level + 1, branches[i].branch, branches, hierarchy);
887 SortBranchesRecursively(1,
"", branches, &hierarchy);
893 const bool machine_readable = (args.find(
'x') != args.end());
894 const bool branch_hierarchy = (args.find(
'B') != args.end());
897 const bool history_read_write =
false;
904 if (branch_hierarchy) {
906 if (!env->history->ListBranches(&branch_list)) {
908 "failed to list branches in history database");
913 if (machine_readable) {
914 PrintMachineReadableBranchList(branch_hierarchy);
916 PrintHumanReadableBranchList(branch_hierarchy);
921 if (!env->history->List(&tags)) {
923 "failed to list tags in history database");
927 if (machine_readable) {
928 PrintMachineReadableTagList(tags);
930 PrintHumanReadableTagList(tags);
943 r.push_back(Parameter::Mandatory(
'n',
"name of the tag to be inspected"));
944 r.push_back(Parameter::Switch(
'x',
"machine readable output"));
948 std::string CommandInfoTag::HumanReadableFilesize(
const size_t filesize)
const {
949 const size_t kiB = 1024;
950 const size_t MiB = kiB * 1024;
951 const size_t GiB = MiB * 1024;
953 if (filesize > GiB) {
955 }
else if (filesize > MiB) {
957 }
else if (filesize > kiB) {
964 void CommandInfoTag::PrintHumanReadableInfo(
968 "Revision: %" PRIu64
"\n"
978 HumanReadableFilesize(tag.
size).c_str(),
983 const std::string tag_name = *args.find(
'n')->second;
984 const bool machine_readable = (args.find(
'x') != args.end());
987 const bool history_read_write =
false;
995 const bool found = env->history->GetByName(tag_name, &tag);
1002 if (machine_readable) {
1003 PrintTagMachineReadable(tag);
1005 PrintHumanReadableInfo(tag);
1017 r.push_back(Parameter::Optional(
'n',
"name of the tag to be republished"));
1022 const bool undo_rollback = (args.find(
'n') == args.end());
1023 const std::string tag_name =
1024 (!undo_rollback) ? *args.find(
'n')->second : CommandTag::kPreviousHeadTag;
1027 const bool history_read_write =
true;
1036 const bool found = env->history->GetByName(tag_name, &target_tag);
1038 if (undo_rollback) {
1040 "only one anonymous rollback supported - "
1041 "perhaps you want to provide a tag name?");
1048 if (target_tag.
branch !=
"") {
1050 "rollback is only supported on the default branch");
1056 if (!env->history->ListTagsAffectedByRollback(tag_name, &affected_tags)) {
1058 "failed to list condemned tags prior to rollback to '%s'",
1064 const uint64_t current_revision = env->manifest->revision();
1066 if (target_tag.
revision == current_revision) {
1068 "not rolling back to current head (%" PRIu64
")",
1076 const bool catalog_read_write =
true;
1078 dynamic_cast<catalog::WritableCatalog *>(
1079 GetCatalog(env->repository_url, target_tag.
root_hash,
1080 catalog_path.
path(), catalog_read_write)));
1081 if (!catalog.IsValid()) {
1091 "not rolling back to outdated and "
1092 "incompatible catalog schema (%.1f < %.1f)",
1099 catalog->Transaction();
1100 catalog->UpdateLastModified();
1101 catalog->SetRevision(current_revision + 1);
1102 catalog->SetPreviousRevision(env->manifest->catalog_hash());
1106 if (!UploadCatalogAndUpdateManifest(env.
weak_ref(), catalog.Release())) {
1113 updated_target_tag.
root_hash = env->manifest->catalog_hash();
1114 updated_target_tag.
size = env->manifest->catalog_size();
1115 updated_target_tag.
revision = env->manifest->revision();
1116 updated_target_tag.
timestamp = env->manifest->publish_timestamp();
1117 if (!env->history->Rollback(updated_target_tag)) {
1119 updated_target_tag.
name.c_str());
1122 bool retval = env->history->Vacuum();
1126 if (!UpdateUndoTags(env.
weak_ref(), updated_target_tag, undo_rollback)) {
1132 if (!CloseAndPublishHistory(env.
weak_ref())) {
1137 PrintDeletedTagList(affected_tags);
1142 void CommandRollbackTag::PrintDeletedTagList(
const TagList &tags)
const {
1143 size_t longest_name = 0;
1144 TagList::const_iterator i = tags.begin();
1145 const TagList::const_iterator iend = tags.end();
1146 for (; i != iend; ++i) {
1147 longest_name = std::max(i->name.size(), longest_name);
1151 for (; i != iend; ++i) {
1153 AddPadding(i->name, longest_name).c_str(),
1154 i->root_hash.ToString().c_str());
1168 const bool history_read_write =
true;
1175 if (!env->history->EmptyRecycleBin()) {
1181 if (!CloseAndPublishHistory(env.
weak_ref())) {
int return_code
the return value of the spooler operation
const manifest::Manifest * manifest() const
const std::string & path() const
const std::string repository_url
static SqliteHistory * Open(const std::string &file_name)
std::vector< Parameter > ParameterList
UniquePtr< manifest::Manifest > manifest
std::string ToString(const bool with_suffix=false) const
const history::History * history() const
std::string ToStringWithSuffix() const
std::string CreateTempPath(const std::string &path_prefix, const int mode)
static void InsertCommonParameters(ParameterList *r)
assert((mem||(size==0))&&"Out Of Memory")
string StringifyTime(const time_t seconds, const bool utc)
const std::string & fqrn() const
string StringifyDouble(const double value)
void Set(const T &object)
std::vector< history::History::Tag > TagList
static SqliteHistory * OpenWritable(const std::string &file_name)
static WritableCatalog * AttachFreely(const std::string &root_path, const std::string &file, const shash::Any &catalog_hash, Catalog *parent=NULL, const bool is_not_root=false)
std::vector< history::History::Branch > BranchList
bool FileExists(const std::string &path)
uint64_t GetRevision() const
uint64_t GetLastModified() const
const char * Code2Ascii(const Failures error)
vector< string > SplitString(const string &str, char delim)
static const float kSchemaEpsilon
std::string repository_name() const
const std::string tmp_path
const char kSuffixCatalog
string StringifyInt(const int64_t value)
bool HasPrefix(const string &str, const string &prefix, const bool ignore_case)
PathString root_prefix() const
std::map< char, SharedPtr< std::string > > ArgumentList
Algorithms ParseHashAlgorithm(const string &algorithm_option)
shash::Any history() const
static Catalog * AttachFreely(const std::string &imaginary_mountpoint, const std::string &file, const shash::Any &catalog_hash, Catalog *parent=NULL, const bool is_nested=false)
static SqliteHistory * Create(const std::string &file_name, const std::string &fqrn)
UniquePtr< history::History > history
Any MkFromHexPtr(const HexPtr hex, const char suffix)
int64_t GetFileSize(const std::string &path)
static const float kLatestSupportedSchema
std::string MakePath() const
std::string MakeCanonicalPath(const std::string &path)
void PrintError(const string &message)
UnlinkGuard manifest_path
UniquePtr< upload::Spooler > spooler
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)