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