6 #include "cvmfs_config.h"
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() &&
128 !this->InitVerifyingSignatureManager(pubkey_path)) {
136 ? OpenLocalManifest(env->manifest_path.path())
137 : FetchRemoteManifest(env->repository_url, repo_name, base_hash);
139 if (!env->manifest.
IsValid()) {
145 if (read_write && env->manifest->history().IsNull() && !base_hash.
IsNull()) {
146 env->previous_manifest =
147 FetchRemoteManifest(env->repository_url, repo_name, base_hash);
148 if (!env->previous_manifest.
IsValid()) {
154 "using history database '%s' from previous "
155 "manifest (%s) as basis",
156 env->previous_manifest->history().ToString().c_str(),
157 env->previous_manifest->repository_name().c_str());
158 env->manifest->set_history(env->previous_manifest->history());
159 env->manifest->set_repository_name(
160 env->previous_manifest->repository_name());
164 env->history = GetHistory(env->manifest.
weak_ref(), env->repository_url,
165 env->history_path.path(), read_write);
174 const bool use_file_chunking =
false;
175 const bool generate_legacy_bulk_chunks =
false;
178 generate_legacy_bulk_chunks,
179 use_file_chunking, 0, 0, 0,
181 env->spooler = upload::Spooler::Construct(sd);
204 upload::Spooler::CallbackPtr callback = env->
spooler->RegisterListener(
205 &CommandTag::UploadClosure,
this, &history_hash);
209 env->
spooler->UnregisterListener(callback);
212 if (new_history_hash.
IsNull()) {
217 env->
manifest->set_history(new_history_hash);
227 "exported manifest (%" PRIu64
") with new history '%s'",
234 bool CommandTag::UploadCatalogAndUpdateManifest(
240 const std::string catalog_path = wr_catalog->database_path();
241 env->
manifest->set_ttl(wr_catalog->GetTTL());
242 env->
manifest->set_revision(wr_catalog->GetRevision());
243 env->
manifest->set_publish_timestamp(wr_catalog->GetLastModified());
251 upload::Spooler::CallbackPtr callback = env->
spooler->RegisterListener(
252 &CommandTag::UploadClosure,
this, &catalog_hash);
253 env->
spooler->ProcessCatalog(catalog_path);
256 env->
spooler->UnregisterListener(callback);
259 if (new_catalog_hash.
IsNull()) {
261 catalog_path.c_str());
266 const size_t catalog_size =
GetFileSize(catalog_path);
267 env->
manifest->set_catalog_size(catalog_size);
268 env->
manifest->set_catalog_hash(new_catalog_hash);
271 catalog_size, new_catalog_hash.
ToString().c_str());
288 bool CommandTag::UpdateUndoTags(
290 const bool undo_rollback) {
297 if (!env->
history->Remove(CommandTag::kPreviousHeadTag)) {
303 if (env->
history->GetByName(CommandTag::kHeadTag, ¤t_head)) {
305 if (!env->
history->Remove(CommandTag::kHeadTag)) {
311 if (!undo_rollback) {
312 current_old_head = current_head;
313 current_old_head.
name = CommandTag::kPreviousHeadTag;
314 current_old_head.
description = CommandTag::kPreviousHeadTagDescription;
315 if (!env->
history->Insert(current_old_head)) {
323 current_head = current_head_template;
324 current_head.
name = CommandTag::kHeadTag;
325 current_head.description = CommandTag::kHeadTagDescription;
326 if (!env->
history->Insert(current_head)) {
334 bool CommandTag::FetchObject(
const std::string &repository_url,
336 const std::string &destination_path)
const {
340 const std::string url = repository_url +
"/data/" + object_hash.
MakePath();
344 dl_retval = download_manager()->Fetch(&download_object);
357 const std::string &repository_url,
358 const std::string &history_path,
359 const bool read_write)
const {
363 if (history_hash.
IsNull()) {
366 if (NULL == history) {
371 if (!FetchObject(repository_url, history_hash, history_path)) {
377 if (NULL == history) {
379 history_path.c_str());
380 unlink(history_path.c_str());
392 const std::string catalog_path,
393 const bool read_write)
const {
395 if (!FetchObject(repository_url, catalog_hash, catalog_path)) {
399 const std::string catalog_root_path =
"";
401 catalog_root_path, catalog_path, catalog_hash)
403 catalog_root_path, catalog_path, catalog_hash);
406 void CommandTag::PrintTagMachineReadable(
416 std::string CommandTag::AddPadding(
const std::string &str,
const size_t padding,
417 const bool align_right,
418 const std::string &fill_char)
const {
419 assert(str.size() <= padding);
420 std::string result(str);
421 result.resize(padding);
422 const size_t pos = (align_right) ? 0 : str.size();
423 const size_t padding_width = padding - str.size();
424 for (
size_t i = 0; i < padding_width; ++i) result.insert(pos, fill_char);
428 bool CommandTag::IsUndoTagName(
const std::string &tag_name)
const {
429 return tag_name == CommandTag::kHeadTag ||
430 tag_name == CommandTag::kPreviousHeadTag;
439 r.push_back(Parameter::Optional(
'd',
"space separated tags to be deleted"));
440 r.push_back(Parameter::Optional(
'a',
"name of the new tag"));
441 r.push_back(Parameter::Optional(
'D',
"description of the tag"));
442 r.push_back(Parameter::Optional(
'B',
"branch of the new tag"));
443 r.push_back(Parameter::Optional(
'P',
"predecessor branch"));
444 r.push_back(Parameter::Optional(
'h',
"root hash of the new tag"));
445 r.push_back(Parameter::Switch(
'x',
"maintain undo tags"));
450 if ((args.find(
'd') == args.end()) && (args.find(
'a') == args.end()) &&
451 (args.find(
'x') == args.end())) {
457 const bool history_read_write =
true;
465 if (args.find(
'd') != args.end()) {
466 retval = RemoveTags(args, env.
weak_ref());
467 if (retval != 0)
return retval;
469 if ((args.find(
'a') != args.end()) || (args.find(
'x') != args.end())) {
470 retval = AddNewTag(args, env.
weak_ref());
471 if (retval != 0)
return retval;
475 if (!CloseAndPublishHistory(env.
weak_ref())) {
482 const std::string tag_name =
483 (args.find(
'a') != args.end()) ? *args.find(
'a')->second :
"";
484 const std::string tag_description =
485 (args.find(
'D') != args.end()) ? *args.find(
'D')->second :
"";
486 const bool undo_tags = (args.find(
'x') != args.end());
487 const std::string root_hash_string =
488 (args.find(
'h') != args.end()) ? *args.find(
'h')->second :
"";
489 const std::string branch_name =
490 (args.find(
'B') != args.end()) ? *args.find(
'B')->second :
"";
491 const std::string previous_branch_name =
492 (args.find(
'P') != args.end()) ? *args.find(
'P')->second :
"";
494 if (tag_name.find(
" ") != std::string::npos) {
499 assert(!tag_name.empty() || undo_tags);
501 if (IsUndoTagName(tag_name)) {
508 shash::Any root_hash = GetTagRootHash(env, root_hash_string);
516 const bool catalog_read_write =
false;
528 "cannot tag catalog '%s' that is not a "
536 tag_template.
name =
"<template>";
541 tag_template.
branch = branch_name;
545 if (!tag_name.empty()) {
546 tag_template.
name = tag_name;
547 const bool user_provided_hash = (!root_hash_string.empty());
552 previous_branch_name,
554 if (!env->
history->InsertBranch(branch)) {
556 tag_template.
branch.c_str());
561 if (!ManipulateTag(env, tag_template, user_provided_hash)) {
567 if (undo_tags && !UpdateUndoTags(env, tag_template)) {
575 Environment *env,
const std::string &root_hash_string)
const {
578 if (root_hash_string.empty()) {
580 "no catalog hash provided, using hash"
581 "of current HEAD catalog (%s)",
582 env->
manifest->catalog_hash().ToString().c_str());
583 root_hash = env->
manifest->catalog_hash();
589 "failed to read provided catalog hash '%s'",
590 root_hash_string.c_str());
599 const bool user_provided_hash) {
600 const std::string &tag_name = tag_template.
name;
603 if (!env->
history->Exists(tag_name)) {
604 return CreateTag(env, tag_template);
608 if (!user_provided_hash) {
610 "a tag with the name '%s' already exists. Do you want to move it? "
617 return MoveTag(env, tag_template);
623 const std::string &tag_name = tag_template.
name;
628 if (!env->
history->GetByName(tag_name, &old_tag)) {
648 if (!env->
history->Remove(tag_name)) {
653 if (!env->
history->PruneBranches()) {
657 bool retval = env->
history->Vacuum();
665 return CreateTag(env, new_tag);
670 if (!env->
history->Insert(new_tag)) {
672 new_tag.
name.c_str());
680 typedef std::vector<std::string> TagNames;
681 const std::string tags_to_delete = *args.find(
'd')->second;
683 const TagNames condemned_tags =
SplitString(tags_to_delete,
' ');
686 TagNames::const_iterator i = condemned_tags.begin();
687 const TagNames::const_iterator iend = condemned_tags.end();
688 for (; i != iend; ++i) {
689 if (IsUndoTagName(*i)) {
691 "undo tags are handled internally and cannot be deleted");
697 condemned_tags.size());
700 bool all_exist =
true;
701 for (i = condemned_tags.begin(); i != iend; ++i) {
702 if (!env->
history->Exists(*i)) {
712 i = condemned_tags.begin();
713 env->
history->BeginTransaction();
714 for (; i != iend; ++i) {
717 const bool found_tag = env->
history->GetByName(*i, &condemned_tag);
720 condemned_tag.
name.c_str(),
724 if (!env->
history->Remove(*i)) {
730 bool retval = env->
history->PruneBranches();
733 "failed to prune unused branches from history");
736 env->
history->CommitTransaction();
737 retval = env->
history->Vacuum();
749 r.push_back(Parameter::Switch(
'x',
"machine readable output"));
750 r.push_back(Parameter::Switch(
'B',
"print branch hierarchy"));
754 void CommandListTags::PrintHumanReadableTagList(
757 const std::string name_label =
"Name";
758 const std::string rev_label =
"Revision";
759 const std::string time_label =
"Timestamp";
760 const std::string branch_label =
"Branch";
761 const std::string desc_label =
"Description";
764 TagList::const_reverse_iterator i = tags.rbegin();
765 const TagList::const_reverse_iterator iend = tags.rend();
766 size_t max_name_len = name_label.size();
767 size_t max_rev_len = rev_label.size();
768 size_t max_time_len = desc_label.size();
769 size_t max_branch_len = branch_label.size();
770 for (; i != iend; ++i) {
771 max_name_len = std::max(max_name_len, i->name.size());
772 max_rev_len = std::max(max_rev_len,
StringifyInt(i->revision).size());
774 std::max(max_time_len,
StringifyTime(i->timestamp,
true).size());
775 max_branch_len = std::max(max_branch_len, i->branch.size());
780 "%s \u2502 %s \u2502 %s \u2502 %s \u2502 %s",
781 AddPadding(name_label, max_name_len).c_str(),
782 AddPadding(rev_label, max_rev_len).c_str(),
783 AddPadding(time_label, max_time_len).c_str(),
784 AddPadding(branch_label, max_branch_len).c_str(),
787 "%s\u2500\u253C\u2500%s\u2500\u253C\u2500%s"
788 "\u2500\u253C\u2500%s\u2500\u253C\u2500%s",
789 AddPadding(
"", max_name_len,
false,
"\u2500").c_str(),
790 AddPadding(
"", max_rev_len,
false,
"\u2500").c_str(),
791 AddPadding(
"", max_time_len,
false,
"\u2500").c_str(),
792 AddPadding(
"", max_branch_len,
false,
"\u2500").c_str(),
793 AddPadding(
"", desc_label.size() + 1,
false,
"\u2500").c_str());
797 for (; i != iend; ++i) {
800 "%s \u2502 %s \u2502 %s \u2502 %s \u2502 %s",
801 AddPadding(i->name, max_name_len).c_str(),
802 AddPadding(
StringifyInt(i->revision), max_rev_len,
true).c_str(),
803 AddPadding(
StringifyTime(i->timestamp,
true), max_time_len).c_str(),
804 AddPadding(i->branch, max_branch_len).c_str(),
805 i->description.c_str());
810 "%s\u2500\u2534\u2500%s\u2500\u2534\u2500%s"
811 "\u2500\u2534\u2500%s\u2500\u2534\u2500%s",
812 AddPadding(
"", max_name_len,
false,
"\u2500").c_str(),
813 AddPadding(
"", max_rev_len,
false,
"\u2500").c_str(),
814 AddPadding(
"", max_time_len,
false,
"\u2500").c_str(),
815 AddPadding(
"", max_branch_len,
false,
"\u2500").c_str(),
816 AddPadding(
"", desc_label.size() + 1,
false,
"\u2500").c_str());
822 void CommandListTags::PrintMachineReadableTagList(
const TagList &tags)
const {
823 TagList::const_iterator i = tags.begin();
824 const TagList::const_iterator iend = tags.end();
825 for (; i != iend; ++i) {
826 PrintTagMachineReadable(*i);
831 void CommandListTags::PrintHumanReadableBranchList(
834 unsigned N = branches.size();
835 for (
unsigned i = 0; i < N; ++i) {
836 for (
unsigned l = 0; l < branches[i].level; ++l) {
838 ((l + 1) == branches[i].level) ?
"\u251c " :
"\u2502 ");
841 branches[i].branch.branch.c_str(),
842 branches[i].branch.initial_revision);
847 void CommandListTags::PrintMachineReadableBranchList(
850 unsigned N = branches.size();
851 for (
unsigned i = 0; i < N; ++i) {
854 AddPadding(
"", branches[i].level,
false,
" ").c_str(),
855 branches[i].branch.branch.c_str(),
856 branches[i].branch.initial_revision);
861 void CommandListTags::SortBranchesRecursively(
863 const string &parent_branch,
869 unsigned N = branches.size();
870 for (
unsigned i = 0; i < N; ++i) {
871 if (branches[i].branch ==
"")
873 if (branches[i].parent == parent_branch) {
874 hierarchy->push_back(
BranchLevel(branches[i], level));
875 SortBranchesRecursively(
876 level + 1, branches[i].branch, branches, hierarchy);
888 SortBranchesRecursively(1,
"", branches, &hierarchy);
894 const bool machine_readable = (args.find(
'x') != args.end());
895 const bool branch_hierarchy = (args.find(
'B') != args.end());
898 const bool history_read_write =
false;
905 if (branch_hierarchy) {
907 if (!env->history->ListBranches(&branch_list)) {
909 "failed to list branches in history database");
914 if (machine_readable) {
915 PrintMachineReadableBranchList(branch_hierarchy);
917 PrintHumanReadableBranchList(branch_hierarchy);
922 if (!env->history->List(&tags)) {
924 "failed to list tags in history database");
928 if (machine_readable) {
929 PrintMachineReadableTagList(tags);
931 PrintHumanReadableTagList(tags);
944 r.push_back(Parameter::Mandatory(
'n',
"name of the tag to be inspected"));
945 r.push_back(Parameter::Switch(
'x',
"machine readable output"));
949 std::string CommandInfoTag::HumanReadableFilesize(
const size_t filesize)
const {
950 const size_t kiB = 1024;
951 const size_t MiB = kiB * 1024;
952 const size_t GiB = MiB * 1024;
954 if (filesize > GiB) {
956 }
else if (filesize > MiB) {
958 }
else if (filesize > kiB) {
965 void CommandInfoTag::PrintHumanReadableInfo(
969 "Revision: %" PRIu64
"\n"
979 HumanReadableFilesize(tag.
size).c_str(),
984 const std::string tag_name = *args.find(
'n')->second;
985 const bool machine_readable = (args.find(
'x') != args.end());
988 const bool history_read_write =
false;
996 const bool found = env->history->GetByName(tag_name, &tag);
1003 if (machine_readable) {
1004 PrintTagMachineReadable(tag);
1006 PrintHumanReadableInfo(tag);
1018 r.push_back(Parameter::Optional(
'n',
"name of the tag to be republished"));
1023 const bool undo_rollback = (args.find(
'n') == args.end());
1024 const std::string tag_name =
1025 (!undo_rollback) ? *args.find(
'n')->second : CommandTag::kPreviousHeadTag;
1028 const bool history_read_write =
true;
1037 const bool found = env->history->GetByName(tag_name, &target_tag);
1039 if (undo_rollback) {
1041 "only one anonymous rollback supported - "
1042 "perhaps you want to provide a tag name?");
1049 if (target_tag.
branch !=
"") {
1051 "rollback is only supported on the default branch");
1057 if (!env->history->ListTagsAffectedByRollback(tag_name, &affected_tags)) {
1059 "failed to list condemned tags prior to rollback to '%s'",
1065 const uint64_t current_revision = env->manifest->revision();
1067 if (target_tag.
revision == current_revision) {
1069 "not rolling back to current head (%" PRIu64
")",
1077 const bool catalog_read_write =
true;
1079 dynamic_cast<catalog::WritableCatalog *>(
1080 GetCatalog(env->repository_url, target_tag.
root_hash,
1081 catalog_path.
path(), catalog_read_write)));
1082 if (!catalog.IsValid()) {
1092 "not rolling back to outdated and "
1093 "incompatible catalog schema (%.1f < %.1f)",
1100 catalog->Transaction();
1101 catalog->UpdateLastModified();
1102 catalog->SetRevision(current_revision + 1);
1103 catalog->SetPreviousRevision(env->manifest->catalog_hash());
1107 if (!UploadCatalogAndUpdateManifest(env.
weak_ref(), catalog.Release())) {
1114 updated_target_tag.
root_hash = env->manifest->catalog_hash();
1115 updated_target_tag.
size = env->manifest->catalog_size();
1116 updated_target_tag.
revision = env->manifest->revision();
1117 updated_target_tag.
timestamp = env->manifest->publish_timestamp();
1118 if (!env->history->Rollback(updated_target_tag)) {
1120 updated_target_tag.
name.c_str());
1123 bool retval = env->history->Vacuum();
1127 if (!UpdateUndoTags(env.
weak_ref(), updated_target_tag, undo_rollback)) {
1133 if (!CloseAndPublishHistory(env.
weak_ref())) {
1138 PrintDeletedTagList(affected_tags);
1143 void CommandRollbackTag::PrintDeletedTagList(
const TagList &tags)
const {
1144 size_t longest_name = 0;
1145 TagList::const_iterator i = tags.begin();
1146 const TagList::const_iterator iend = tags.end();
1147 for (; i != iend; ++i) {
1148 longest_name = std::max(i->name.size(), longest_name);
1152 for (; i != iend; ++i) {
1154 AddPadding(i->name, longest_name).c_str(),
1155 i->root_hash.ToString().c_str());
1169 const bool history_read_write =
true;
1176 if (!env->history->EmptyRecycleBin()) {
1182 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,...)