Line |
Branch |
Exec |
Source |
1 |
|
|
/** |
2 |
|
|
* This file is part of the CernVM File System. |
3 |
|
|
*/ |
4 |
|
|
|
5 |
|
|
|
6 |
|
|
#include "cmd_diff.h" |
7 |
|
|
|
8 |
|
|
#ifndef __STDC_FORMAT_MACROS |
9 |
|
|
#define __STDC_FORMAT_MACROS |
10 |
|
|
#endif |
11 |
|
|
|
12 |
|
|
#include <inttypes.h> |
13 |
|
|
|
14 |
|
|
#include <cassert> |
15 |
|
|
#include <string> |
16 |
|
|
#include <vector> |
17 |
|
|
|
18 |
|
|
#include "catalog_counters.h" |
19 |
|
|
#include "directory_entry.h" |
20 |
|
|
#include "history.h" |
21 |
|
|
#include "publish/except.h" |
22 |
|
|
#include "publish/repository.h" |
23 |
|
|
#include "publish/settings.h" |
24 |
|
|
#include "sanitizer.h" |
25 |
|
|
#include "util/logging.h" |
26 |
|
|
#include "util/pointer.h" |
27 |
|
|
#include "util/string.h" |
28 |
|
|
|
29 |
|
|
namespace { |
30 |
|
|
|
31 |
|
|
class DiffReporter : public publish::DiffListener { |
32 |
|
|
public: |
33 |
|
✗ |
DiffReporter(bool show_header, bool machine_readable, bool ignore_timediff) |
34 |
|
✗ |
: show_header_(show_header) |
35 |
|
✗ |
, machine_readable_(machine_readable) |
36 |
|
✗ |
, ignore_timediff_(ignore_timediff) |
37 |
|
|
{} |
38 |
|
✗ |
virtual ~DiffReporter() {} |
39 |
|
|
|
40 |
|
|
|
41 |
|
✗ |
virtual void OnInit(const history::History::Tag &from_tag, |
42 |
|
|
const history::History::Tag &to_tag) |
43 |
|
|
{ |
44 |
|
✗ |
if (!show_header_) |
45 |
|
✗ |
return; |
46 |
|
|
|
47 |
|
✗ |
if (machine_readable_) { |
48 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
49 |
|
|
"# line descriptor: A - add, R - remove, M - modify, " |
50 |
|
|
"S - statistics; modify flags: S - size, M - mode, T - timestamp, " |
51 |
|
|
"C - content, L - symlink target; entry types: F - regular file, " |
52 |
|
|
"S - symbolic link, D - directory, N - nested catalog"); |
53 |
|
|
} else { |
54 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, |
55 |
|
|
"DELTA: %s/r%" PRIu64 " (%s) --> %s/r%" PRIu64 " (%s)", |
56 |
|
|
from_tag.name.c_str(), from_tag.revision, |
57 |
|
|
StringifyTime(from_tag.timestamp, true).c_str(), |
58 |
|
|
to_tag.name.c_str(), to_tag.revision, |
59 |
|
|
StringifyTime(to_tag.timestamp, true).c_str()); |
60 |
|
|
} |
61 |
|
|
} |
62 |
|
|
|
63 |
|
|
|
64 |
|
✗ |
virtual void OnStats(const catalog::DeltaCounters &delta) { |
65 |
|
✗ |
std::string operation = machine_readable_ ? "S " : "d("; |
66 |
|
✗ |
std::string type_file = machine_readable_ ? "F" : "# regular files):"; |
67 |
|
✗ |
std::string type_symlink = machine_readable_ ? "S" : "# symlinks):"; |
68 |
|
✗ |
std::string type_directory = machine_readable_ ? "D" : "# directories):"; |
69 |
|
✗ |
std::string type_catalog = machine_readable_ ? "N" : "# catalogs):"; |
70 |
|
✗ |
int64_t diff_file = delta.self.regular_files + delta.subtree.regular_files; |
71 |
|
✗ |
int64_t diff_symlink = delta.self.symlinks + delta.subtree.symlinks; |
72 |
|
✗ |
int64_t diff_catalog = delta.self.nested_catalogs + |
73 |
|
✗ |
delta.subtree.nested_catalogs; |
74 |
|
|
// Nested catalogs make internally two directory entries |
75 |
|
✗ |
int64_t diff_directory = delta.self.directories + |
76 |
|
✗ |
delta.subtree.directories - diff_catalog; |
77 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s%s %" PRId64, operation.c_str(), |
78 |
|
|
type_file.c_str(), diff_file); |
79 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s%s %" PRId64, operation.c_str(), |
80 |
|
|
type_symlink.c_str(), diff_symlink); |
81 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s%s %" PRId64, operation.c_str(), |
82 |
|
|
type_directory.c_str(), diff_directory); |
83 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s%s %" PRId64, operation.c_str(), |
84 |
|
|
type_catalog.c_str(), diff_catalog); |
85 |
|
|
} |
86 |
|
|
|
87 |
|
|
|
88 |
|
✗ |
virtual void OnAdd(const std::string &path, |
89 |
|
|
const catalog::DirectoryEntry &entry) |
90 |
|
|
{ |
91 |
|
✗ |
std::string operation = machine_readable_ ? "A" : "add"; |
92 |
|
✗ |
if (machine_readable_) { |
93 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "%s %s %s", |
94 |
|
|
operation.c_str(), PrintEntryType(entry).c_str(), path.c_str()); |
95 |
|
✗ |
if (!entry.IsDirectory()) { |
96 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, " +%" PRIu64, entry.size()); |
97 |
|
|
} else { |
98 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "\n"); |
99 |
|
|
} |
100 |
|
|
} else { |
101 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s %s %s +%" PRIu64 " bytes", |
102 |
|
|
path.c_str(), operation.c_str(), |
103 |
|
|
PrintEntryType(entry).c_str(), entry.size()); |
104 |
|
|
} |
105 |
|
|
} |
106 |
|
|
|
107 |
|
|
|
108 |
|
✗ |
virtual void OnRemove(const std::string &path, |
109 |
|
|
const catalog::DirectoryEntry &entry) |
110 |
|
|
{ |
111 |
|
✗ |
std::string operation = machine_readable_ ? "R" : "remove"; |
112 |
|
✗ |
if (machine_readable_) { |
113 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout | kLogNoLinebreak, "%s %s %s", |
114 |
|
|
operation.c_str(), PrintEntryType(entry).c_str(), path.c_str()); |
115 |
|
✗ |
if (!entry.IsDirectory()) { |
116 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, " -%" PRIu64, entry.size()); |
117 |
|
|
} else { |
118 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, ""); |
119 |
|
|
} |
120 |
|
|
} else { |
121 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s %s %s -%" PRIu64 " bytes", |
122 |
|
|
path.c_str(), operation.c_str(), |
123 |
|
|
PrintEntryType(entry).c_str(), entry.size()); |
124 |
|
|
} |
125 |
|
|
} |
126 |
|
|
|
127 |
|
|
|
128 |
|
✗ |
virtual void OnModify(const std::string &path, |
129 |
|
|
const catalog::DirectoryEntry &entry_from, |
130 |
|
|
const catalog::DirectoryEntry &entry_to) |
131 |
|
|
{ |
132 |
|
|
catalog::DirectoryEntryBase::Differences diff = |
133 |
|
✗ |
entry_from.CompareTo(entry_to); |
134 |
|
✗ |
if (ignore_timediff_) { |
135 |
|
✗ |
diff = diff & ~catalog::DirectoryEntryBase::Difference::kMtime; |
136 |
|
✗ |
if (diff == 0) |
137 |
|
✗ |
return; |
138 |
|
|
} |
139 |
|
✗ |
if (entry_from.IsDirectory() || entry_to.IsDirectory()) { |
140 |
|
✗ |
diff = diff & ~catalog::DirectoryEntryBase::Difference::kSize; |
141 |
|
✗ |
if (diff == 0) |
142 |
|
✗ |
return; |
143 |
|
|
} |
144 |
|
|
|
145 |
|
✗ |
std::string type_from = PrintEntryType(entry_from); |
146 |
|
✗ |
std::string type_to = PrintEntryType(entry_to); |
147 |
|
✗ |
std::string type = type_from; |
148 |
|
✗ |
if (type_from != type_to) { |
149 |
|
✗ |
type += machine_readable_ ? type_to : ("->" + type_to); |
150 |
|
|
} |
151 |
|
|
|
152 |
|
✗ |
std::string operation = machine_readable_ ? "M" : "modify"; |
153 |
|
✗ |
if (machine_readable_) { |
154 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s%s %s %s", operation.c_str(), |
155 |
|
|
PrintDifferences(diff).c_str(), type.c_str(), path.c_str()); |
156 |
|
|
} else { |
157 |
|
✗ |
LogCvmfs(kLogCvmfs, kLogStdout, "%s %s %s%s", path.c_str(), |
158 |
|
|
operation.c_str(), type.c_str(), PrintDifferences(diff).c_str()); |
159 |
|
|
} |
160 |
|
|
} |
161 |
|
|
|
162 |
|
|
private: |
163 |
|
✗ |
std::string PrintDifferences(catalog::DirectoryEntryBase::Differences diff) { |
164 |
|
✗ |
std::vector<std::string> result_list; |
165 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kName) |
166 |
|
✗ |
result_list.push_back(machine_readable_ ? "N" : "name"); |
167 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kLinkcount) |
168 |
|
✗ |
result_list.push_back(machine_readable_ ? "I" : "link-count"); |
169 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kSize) |
170 |
|
✗ |
result_list.push_back(machine_readable_ ? "S" : "size"); |
171 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kMode) |
172 |
|
✗ |
result_list.push_back(machine_readable_ ? "M" : "mode"); |
173 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kMtime) |
174 |
|
✗ |
result_list.push_back(machine_readable_ ? "T" : "timestamp"); |
175 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kSymlink) |
176 |
|
✗ |
result_list.push_back(machine_readable_ ? "L" : "symlink-target"); |
177 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kChecksum) |
178 |
|
✗ |
result_list.push_back(machine_readable_ ? "C" : "content"); |
179 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kHardlinkGroup) |
180 |
|
✗ |
result_list.push_back(machine_readable_ ? "G" : "hardlink-group"); |
181 |
|
✗ |
if (diff & |
182 |
|
|
catalog::DirectoryEntryBase::Difference::kNestedCatalogTransitionFlags) |
183 |
|
|
{ |
184 |
|
✗ |
result_list.push_back(machine_readable_ ? "N" : "nested-catalog"); |
185 |
|
|
} |
186 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kChunkedFileFlag) |
187 |
|
✗ |
result_list.push_back(machine_readable_ ? "P" : "chunked-file"); |
188 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kHasXattrsFlag) |
189 |
|
✗ |
result_list.push_back(machine_readable_ ? "X" : "xattrs"); |
190 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kExternalFileFlag) |
191 |
|
✗ |
result_list.push_back(machine_readable_ ? "E" : "external-file"); |
192 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kBindMountpointFlag) |
193 |
|
✗ |
result_list.push_back(machine_readable_ ? "B" : "bind-mountpoint"); |
194 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kHiddenFlag) |
195 |
|
✗ |
result_list.push_back(machine_readable_ ? "H" : "hidden"); |
196 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kDirectIoFlag) |
197 |
|
✗ |
result_list.push_back(machine_readable_ ? "D" : "direct-io"); |
198 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kUid) |
199 |
|
✗ |
result_list.push_back(machine_readable_ ? "U" : "uid"); |
200 |
|
✗ |
if (diff & catalog::DirectoryEntryBase::Difference::kGid) |
201 |
|
✗ |
result_list.push_back(machine_readable_ ? "R" : "gid"); |
202 |
|
|
|
203 |
|
✗ |
return machine_readable_ ? ("[" + JoinStrings(result_list, "") + "]") |
204 |
|
✗ |
: (" [" + JoinStrings(result_list, ", ") + "]"); |
205 |
|
|
} |
206 |
|
|
|
207 |
|
✗ |
std::string PrintEntryType(const catalog::DirectoryEntry &entry) { |
208 |
|
✗ |
if (entry.IsRegular()) return machine_readable_ ? "F" : "file"; |
209 |
|
✗ |
else if (entry.IsLink()) return machine_readable_ ? "S" : "symlink"; |
210 |
|
✗ |
else if (entry.IsDirectory()) return machine_readable_ ? "D" : "directory"; |
211 |
|
|
else |
212 |
|
✗ |
return machine_readable_ ? "U" : "unknown"; |
213 |
|
|
} |
214 |
|
|
|
215 |
|
|
bool show_header_; |
216 |
|
|
bool machine_readable_; |
217 |
|
|
bool ignore_timediff_; |
218 |
|
|
}; // class DiffReporter |
219 |
|
|
|
220 |
|
|
} // anonymous namespace |
221 |
|
|
|
222 |
|
|
|
223 |
|
|
namespace publish { |
224 |
|
|
|
225 |
|
✗ |
int CmdDiff::Main(const Options &options) { |
226 |
|
✗ |
SettingsBuilder builder; |
227 |
|
|
|
228 |
|
✗ |
if (options.Has("worktree")) { |
229 |
|
|
UniquePtr<SettingsPublisher> settings(builder.CreateSettingsPublisher( |
230 |
|
✗ |
options.plain_args().empty() ? "" : options.plain_args()[0].value_str)); |
231 |
|
✗ |
settings->SetIsSilent(true); |
232 |
|
✗ |
settings->GetTransaction()->SetDryRun(true); |
233 |
|
✗ |
settings->GetTransaction()->SetPrintChangeset(true); |
234 |
|
✗ |
Publisher publisher(*settings); |
235 |
|
✗ |
publisher.Sync(); |
236 |
|
✗ |
return 0; |
237 |
|
|
} |
238 |
|
|
|
239 |
|
|
SettingsRepository settings = builder.CreateSettingsRepository( |
240 |
|
✗ |
options.plain_args().empty() ? "" : options.plain_args()[0].value_str); |
241 |
|
|
|
242 |
|
✗ |
std::string from = options.GetStringDefault("from", "trunk-previous"); |
243 |
|
✗ |
std::string to = options.GetStringDefault("to", "trunk"); |
244 |
|
|
|
245 |
|
✗ |
if (options.Has("keychain")) { |
246 |
|
✗ |
settings.GetKeychain()->SetKeychainDir(options.GetString("keychain")); |
247 |
|
|
} |
248 |
|
✗ |
Repository repository(settings); |
249 |
|
|
|
250 |
|
✗ |
DiffReporter diff_reporter(options.Has("header"), |
251 |
|
✗ |
options.Has("machine-readable"), |
252 |
|
✗ |
options.Has("ignore-timediff")); |
253 |
|
✗ |
repository.Diff(from, to, &diff_reporter); |
254 |
|
|
|
255 |
|
✗ |
return 0; |
256 |
|
|
} |
257 |
|
|
|
258 |
|
|
} // namespace publish |
259 |
|
|
|