19 using namespace swissknife;
21 const std::string CommandTag::kHeadTag =
"trunk";
22 const std::string CommandTag::kPreviousHeadTag =
"trunk-previous";
24 const std::string CommandTag::kHeadTagDescription =
"current HEAD";
26 CommandTag::kPreviousHeadTagDescription =
"default undo target";
29 r->push_back(Parameter::Mandatory(
'w',
"repository directory / url"));
30 r->push_back(Parameter::Mandatory(
't',
"temporary scratch directory"));
31 r->push_back(Parameter::Optional(
'p',
"public key of the repository"));
32 r->push_back(Parameter::Optional(
'f',
"fully qualified repository name"));
33 r->push_back(Parameter::Optional(
'r',
"spooler definition string"));
34 r->push_back(Parameter::Optional(
'm',
"(unsigned) manifest file to edit"));
35 r->push_back(Parameter::Optional(
'b',
"mounted repository base hash"));
37 Parameter::Optional(
'e',
"hash algorithm to use (default SHA1)"));
38 r->push_back(Parameter::Switch(
'L',
"follow HTTP redirects"));
39 r->push_back(Parameter::Optional(
'P',
"session_token_file"));
40 r->push_back(Parameter::Optional(
'@',
"proxy url"));
47 const string spl_definition = (args.find(
'r') == args.end())
50 *args.find(
'r')->second);
51 const string manifest_path = (args.find(
'm') == args.end())
57 *args.find(
'e')->second);
58 const string pubkey_path = (args.find(
'p') == args.end())
61 const shash::Any base_hash = (args.find(
'b') == args.end())
66 const string repo_name = (args.find(
'f') == args.end())
68 : *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()) {
103 PrintError(
"Session token file has to be provided "
104 "when upstream type is gw.");
114 env->manifest_path.Set(manifest_path);
115 env->history_path.Set(
CreateTempPath(tmp_path +
"/history", 0600));
118 const bool follow_redirects = (args.count(
'L') > 0);
119 const std::string &proxy = (args.count(
'@') > 0) ? *args.find(
'@')->second
121 if (!this->InitDownloadManager(follow_redirects, proxy)) {
126 if (!pubkey_path.empty() && !this->InitSignatureManager(pubkey_path)) {
132 env->manifest = (
FileExists(env->manifest_path.path()))
133 ? OpenLocalManifest(env->manifest_path.path())
134 : FetchRemoteManifest(env->repository_url, repo_name,
137 if (!env->manifest.
IsValid()) {
143 if (read_write && env->manifest->history().IsNull() && !base_hash.
IsNull()) {
144 env->previous_manifest = FetchRemoteManifest(env->repository_url, repo_name,
146 if (!env->previous_manifest.
IsValid()) {
152 "using history database '%s' from previous "
153 "manifest (%s) as basis",
154 env->previous_manifest->history().ToString().c_str(),
155 env->previous_manifest->repository_name().c_str());
156 env->manifest->set_history(env->previous_manifest->history());
157 env->manifest->set_repository_name(
158 env->previous_manifest->repository_name());
162 env->history = GetHistory(env->manifest.
weak_ref(), env->repository_url,
163 env->history_path.path(), read_write);
172 const bool use_file_chunking =
false;
173 const bool generate_legacy_bulk_chunks =
false;
176 generate_legacy_bulk_chunks, use_file_chunking, 0, 0, 0,
178 env->spooler = upload::Spooler::Construct(sd);
201 upload::Spooler::CallbackPtr callback = env->
spooler->RegisterListener(
202 &CommandTag::UploadClosure,
this, &history_hash);
206 env->
spooler->UnregisterListener(callback);
209 if (new_history_hash.
IsNull()) {
214 env->
manifest->set_history(new_history_hash);
224 "exported manifest (%" PRIu64
") with new history '%s'",
231 bool CommandTag::UploadCatalogAndUpdateManifest(
237 const std::string catalog_path = wr_catalog->database_path();
238 env->
manifest->set_ttl(wr_catalog->GetTTL());
239 env->
manifest->set_revision(wr_catalog->GetRevision());
240 env->
manifest->set_publish_timestamp(wr_catalog->GetLastModified());
248 upload::Spooler::CallbackPtr callback = env->
spooler->RegisterListener(
249 &CommandTag::UploadClosure,
this, &catalog_hash);
250 env->
spooler->ProcessCatalog(catalog_path);
253 env->
spooler->UnregisterListener(callback);
256 if (new_catalog_hash.
IsNull()) {
258 catalog_path.c_str());
263 const size_t catalog_size =
GetFileSize(catalog_path);
264 env->
manifest->set_catalog_size(catalog_size);
265 env->
manifest->set_catalog_hash(new_catalog_hash);
268 catalog_size, new_catalog_hash.
ToString().c_str());
285 bool CommandTag::UpdateUndoTags(
287 const bool undo_rollback) {
294 if (!env->
history->Remove(CommandTag::kPreviousHeadTag)) {
300 if (env->
history->GetByName(CommandTag::kHeadTag, ¤t_head)) {
302 if (!env->
history->Remove(CommandTag::kHeadTag)) {
308 if (!undo_rollback) {
309 current_old_head = current_head;
310 current_old_head.
name = CommandTag::kPreviousHeadTag;
311 current_old_head.
description = CommandTag::kPreviousHeadTagDescription;
312 if (!env->
history->Insert(current_old_head)) {
320 current_head = current_head_template;
321 current_head.
name = CommandTag::kHeadTag;
322 current_head.description = CommandTag::kHeadTagDescription;
323 if (!env->
history->Insert(current_head)) {
331 bool CommandTag::FetchObject(
const std::string &repository_url,
333 const std::string &destination_path)
const {
337 const std::string url = repository_url +
"/data/" + object_hash.
MakePath();
341 dl_retval = download_manager()->Fetch(&download_object);
354 const std::string &repository_url,
355 const std::string &history_path,
356 const bool read_write)
const {
360 if (history_hash.
IsNull()) {
363 if (NULL == history) {
368 if (!FetchObject(repository_url, history_hash, history_path)) {
374 if (NULL == history) {
376 history_path.c_str());
377 unlink(history_path.c_str());
389 const std::string catalog_path,
390 const bool read_write)
const {
392 if (!FetchObject(repository_url, catalog_hash, catalog_path)) {
396 const std::string catalog_root_path =
"";
398 catalog_root_path, catalog_path, catalog_hash)
400 catalog_root_path, catalog_path, catalog_hash);
403 void CommandTag::PrintTagMachineReadable(
412 std::string CommandTag::AddPadding(
const std::string &str,
const size_t padding,
413 const bool align_right,
414 const std::string &fill_char)
const {
415 assert(str.size() <= padding);
416 std::string result(str);
417 result.resize(padding);
418 const size_t pos = (align_right) ? 0 : str.size();
419 const size_t padding_width = padding - str.size();
420 for (
size_t i = 0; i < padding_width; ++i)
421 result.insert(pos, fill_char);
425 bool CommandTag::IsUndoTagName(
const std::string &tag_name)
const {
426 return tag_name == CommandTag::kHeadTag
427 || tag_name == CommandTag::kPreviousHeadTag;
436 r.push_back(Parameter::Optional(
'd',
"space separated tags to be deleted"));
437 r.push_back(Parameter::Optional(
'a',
"name of the new tag"));
438 r.push_back(Parameter::Optional(
'D',
"description of the tag"));
439 r.push_back(Parameter::Optional(
'B',
"branch of the new tag"));
440 r.push_back(Parameter::Optional(
'P',
"predecessor branch"));
441 r.push_back(Parameter::Optional(
'h',
"root hash of the new tag"));
442 r.push_back(Parameter::Switch(
'x',
"maintain undo tags"));
447 if ((args.find(
'd') == args.end()) && (args.find(
'a') == args.end())
448 && (args.find(
'x') == args.end())) {
454 const bool history_read_write =
true;
462 if (args.find(
'd') != args.end()) {
463 retval = RemoveTags(args, env.
weak_ref());
467 if ((args.find(
'a') != args.end()) || (args.find(
'x') != args.end())) {
468 retval = AddNewTag(args, env.
weak_ref());
474 if (!CloseAndPublishHistory(env.
weak_ref())) {
481 const std::string tag_name = (args.find(
'a') != args.end())
482 ? *args.find(
'a')->second
484 const std::string tag_description = (args.find(
'D') != args.end())
485 ? *args.find(
'D')->second
487 const bool undo_tags = (args.find(
'x') != args.end());
488 const std::string root_hash_string = (args.find(
'h') != args.end())
489 ? *args.find(
'h')->second
491 const std::string branch_name = (args.find(
'B') != args.end())
492 ? *args.find(
'B')->second
494 const std::string previous_branch_name = (args.find(
'P') != args.end())
495 ? *args.find(
'P')->second
498 if (tag_name.find(
" ") != std::string::npos) {
503 assert(!tag_name.empty() || undo_tags);
505 if (IsUndoTagName(tag_name)) {
512 shash::Any root_hash = GetTagRootHash(env, root_hash_string);
520 const bool catalog_read_write =
false;
532 "cannot tag catalog '%s' that is not a "
540 tag_template.
name =
"<template>";
545 tag_template.
branch = branch_name;
549 if (!tag_name.empty()) {
550 tag_template.
name = tag_name;
551 const bool user_provided_hash = (!root_hash_string.empty());
555 tag_template.
branch, previous_branch_name, tag_template.
revision);
556 if (!env->
history->InsertBranch(branch)) {
558 tag_template.
branch.c_str());
563 if (!ManipulateTag(env, tag_template, user_provided_hash)) {
569 if (undo_tags && !UpdateUndoTags(env, tag_template)) {
577 Environment *env,
const std::string &root_hash_string)
const {
580 if (root_hash_string.empty()) {
582 "no catalog hash provided, using hash"
583 "of current HEAD catalog (%s)",
584 env->
manifest->catalog_hash().ToString().c_str());
585 root_hash = env->
manifest->catalog_hash();
591 "failed to read provided catalog hash '%s'",
592 root_hash_string.c_str());
601 const bool user_provided_hash) {
602 const std::string &tag_name = tag_template.
name;
605 if (!env->
history->Exists(tag_name)) {
606 return CreateTag(env, tag_template);
610 if (!user_provided_hash) {
612 "a tag with the name '%s' already exists. Do you want to move it? "
619 return MoveTag(env, tag_template);
624 const std::string &tag_name = tag_template.
name;
629 if (!env->
history->GetByName(tag_name, &old_tag)) {
649 if (!env->
history->Remove(tag_name)) {
654 if (!env->
history->PruneBranches()) {
658 bool retval = env->
history->Vacuum();
666 return CreateTag(env, new_tag);
671 if (!env->
history->Insert(new_tag)) {
673 new_tag.
name.c_str());
681 typedef std::vector<std::string> TagNames;
682 const std::string tags_to_delete = *args.find(
'd')->second;
684 const TagNames condemned_tags =
SplitString(tags_to_delete,
' ');
687 TagNames::const_iterator i = condemned_tags.begin();
688 const TagNames::const_iterator iend = condemned_tags.end();
689 for (; i != iend; ++i) {
690 if (IsUndoTagName(*i)) {
692 "undo tags are handled internally and cannot be deleted");
698 condemned_tags.size());
701 bool all_exist =
true;
702 for (i = condemned_tags.begin(); i != iend; ++i) {
703 if (!env->
history->Exists(*i)) {
713 i = condemned_tags.begin();
714 env->
history->BeginTransaction();
715 for (; i != iend; ++i) {
718 const bool found_tag = env->
history->GetByName(*i, &condemned_tag);
721 condemned_tag.
name.c_str(),
725 if (!env->
history->Remove(*i)) {
731 bool retval = env->
history->PruneBranches();
734 "failed to prune unused branches from history");
737 env->
history->CommitTransaction();
738 retval = env->
history->Vacuum();
750 r.push_back(Parameter::Switch(
'x',
"machine readable output"));
751 r.push_back(Parameter::Switch(
'B',
"print branch hierarchy"));
755 void CommandListTags::PrintHumanReadableTagList(
758 const std::string name_label =
"Name";
759 const std::string rev_label =
"Revision";
760 const std::string time_label =
"Timestamp";
761 const std::string branch_label =
"Branch";
762 const std::string desc_label =
"Description";
765 TagList::const_reverse_iterator i = tags.rbegin();
766 const TagList::const_reverse_iterator iend = tags.rend();
767 size_t max_name_len = name_label.size();
768 size_t max_rev_len = rev_label.size();
769 size_t max_time_len = desc_label.size();
770 size_t max_branch_len = branch_label.size();
771 for (; i != iend; ++i) {
772 max_name_len = std::max(max_name_len, i->name.size());
773 max_rev_len = std::max(max_rev_len,
StringifyInt(i->revision).size());
774 max_time_len = std::max(max_time_len,
776 max_branch_len = std::max(max_branch_len, i->branch.size());
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 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(), i->description.c_str());
808 "%s\u2500\u2534\u2500%s\u2500\u2534\u2500%s"
809 "\u2500\u2534\u2500%s\u2500\u2534\u2500%s",
810 AddPadding(
"", max_name_len,
false,
"\u2500").c_str(),
811 AddPadding(
"", max_rev_len,
false,
"\u2500").c_str(),
812 AddPadding(
"", max_time_len,
false,
"\u2500").c_str(),
813 AddPadding(
"", max_branch_len,
false,
"\u2500").c_str(),
814 AddPadding(
"", desc_label.size() + 1,
false,
"\u2500").c_str());
820 void CommandListTags::PrintMachineReadableTagList(
const TagList &tags)
const {
821 TagList::const_iterator i = tags.begin();
822 const TagList::const_iterator iend = tags.end();
823 for (; i != iend; ++i) {
824 PrintTagMachineReadable(*i);
829 void CommandListTags::PrintHumanReadableBranchList(
831 unsigned N = branches.size();
832 for (
unsigned i = 0; i < N; ++i) {
833 for (
unsigned l = 0; l < branches[i].level; ++l) {
835 ((l + 1) == branches[i].level) ?
"\u251c " :
"\u2502 ");
838 branches[i].branch.branch.c_str(),
839 branches[i].branch.initial_revision);
844 void CommandListTags::PrintMachineReadableBranchList(
846 unsigned N = branches.size();
847 for (
unsigned i = 0; i < N; ++i) {
849 AddPadding(
"", branches[i].level,
false,
" ").c_str(),
850 branches[i].branch.branch.c_str(),
851 branches[i].branch.initial_revision);
856 void CommandListTags::SortBranchesRecursively(
858 const string &parent_branch,
863 unsigned N = branches.size();
864 for (
unsigned i = 0; i < N; ++i) {
865 if (branches[i].branch ==
"")
867 if (branches[i].parent == parent_branch) {
868 hierarchy->push_back(
BranchLevel(branches[i], level));
869 SortBranchesRecursively(level + 1, branches[i].branch, branches,
881 SortBranchesRecursively(1,
"", branches, &hierarchy);
887 const bool machine_readable = (args.find(
'x') != args.end());
888 const bool branch_hierarchy = (args.find(
'B') != args.end());
891 const bool history_read_write =
false;
898 if (branch_hierarchy) {
900 if (!env->history->ListBranches(&branch_list)) {
902 "failed to list branches in history database");
907 if (machine_readable) {
908 PrintMachineReadableBranchList(branch_hierarchy);
910 PrintHumanReadableBranchList(branch_hierarchy);
915 if (!env->history->List(&tags)) {
917 "failed to list tags in history database");
921 if (machine_readable) {
922 PrintMachineReadableTagList(tags);
924 PrintHumanReadableTagList(tags);
937 r.push_back(Parameter::Mandatory(
'n',
"name of the tag to be inspected"));
938 r.push_back(Parameter::Switch(
'x',
"machine readable output"));
942 std::string CommandInfoTag::HumanReadableFilesize(
const size_t filesize)
const {
943 const size_t kiB = 1024;
944 const size_t MiB = kiB * 1024;
945 const size_t GiB = MiB * 1024;
947 if (filesize > GiB) {
949 }
else if (filesize > MiB) {
951 }
else if (filesize > kiB) {
958 void CommandInfoTag::PrintHumanReadableInfo(
962 "Revision: %" PRIu64
"\n"
975 const std::string tag_name = *args.find(
'n')->second;
976 const bool machine_readable = (args.find(
'x') != args.end());
979 const bool history_read_write =
false;
987 const bool found = env->history->GetByName(tag_name, &tag);
994 if (machine_readable) {
995 PrintTagMachineReadable(tag);
997 PrintHumanReadableInfo(tag);
1009 r.push_back(Parameter::Optional(
'n',
"name of the tag to be republished"));
1014 const bool undo_rollback = (args.find(
'n') == args.end());
1015 const std::string tag_name = (!undo_rollback) ? *args.find(
'n')->second
1016 : CommandTag::kPreviousHeadTag;
1019 const bool history_read_write =
true;
1028 const bool found = env->history->GetByName(tag_name, &target_tag);
1030 if (undo_rollback) {
1032 "only one anonymous rollback supported - "
1033 "perhaps you want to provide a tag name?");
1040 if (target_tag.
branch !=
"") {
1042 "rollback is only supported on the default branch");
1048 if (!env->history->ListTagsAffectedByRollback(tag_name, &affected_tags)) {
1050 "failed to list condemned tags prior to rollback to '%s'",
1056 const uint64_t current_revision = env->manifest->revision();
1058 if (target_tag.
revision == current_revision) {
1060 "not rolling back to current head (%" PRIu64
")",
1068 const bool catalog_read_write =
true;
1070 dynamic_cast<catalog::WritableCatalog *>(
1071 GetCatalog(env->repository_url, target_tag.
root_hash,
1072 catalog_path.
path(), catalog_read_write)));
1073 if (!catalog.IsValid()) {
1083 "not rolling back to outdated and "
1084 "incompatible catalog schema (%.1f < %.1f)",
1091 catalog->Transaction();
1092 catalog->UpdateLastModified();
1093 catalog->SetRevision(current_revision + 1);
1094 catalog->SetPreviousRevision(env->manifest->catalog_hash());
1098 if (!UploadCatalogAndUpdateManifest(env.
weak_ref(), catalog.Release())) {
1105 updated_target_tag.
root_hash = env->manifest->catalog_hash();
1106 updated_target_tag.
size = env->manifest->catalog_size();
1107 updated_target_tag.
revision = env->manifest->revision();
1108 updated_target_tag.
timestamp = env->manifest->publish_timestamp();
1109 if (!env->history->Rollback(updated_target_tag)) {
1111 updated_target_tag.
name.c_str());
1114 bool retval = env->history->Vacuum();
1118 if (!UpdateUndoTags(env.
weak_ref(), updated_target_tag, undo_rollback)) {
1124 if (!CloseAndPublishHistory(env.
weak_ref())) {
1129 PrintDeletedTagList(affected_tags);
1134 void CommandRollbackTag::PrintDeletedTagList(
const TagList &tags)
const {
1135 size_t longest_name = 0;
1136 TagList::const_iterator i = tags.begin();
1137 const TagList::const_iterator iend = tags.end();
1138 for (; i != iend; ++i) {
1139 longest_name = std::max(i->name.size(), longest_name);
1143 for (; i != iend; ++i) {
1145 AddPadding(i->name, longest_name).c_str(),
1146 i->root_hash.ToString().c_str());
1160 const bool history_read_write =
true;
1167 if (!env->history->EmptyRecycleBin()) {
1173 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,...)