1 /**
2  * @file svnxx/revision.hpp
3  * @copyright
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  * @endcopyright
23  */
24 
25 #ifndef SVNXX_REVISION_HPP
26 #define SVNXX_REVISION_HPP
27 
28 #include "svn_opt_impl.h"
29 #include "svn_types_impl.h"
30 
31 #include <chrono>
32 #include <cstdint>
33 #include <new>
34 
35 #include "tristate.hpp"
36 
37 namespace apache {
38 namespace subversion {
39 namespace svnxx {
40 
41 /**
42  * @brief A revision, see @ref svn_opt_revision_t.
43  *
44  * The @c revision can represent a revision number, a point in time
45  * in the repository or a property of the working copy or repository
46  * node (see revision::kind).
47  */
48 class revision
49 {
50 public:
51   /**
52    * @brief Revision number type.
53    */
54   enum class number : svn_revnum_t
55     {
56       invalid = SVN_INVALID_REVNUM, ///< Invalid revision number.
57     };
58 
59   /**
60    * @brief Revision by date/time uses the system clock.
61    */
62   template<typename Duration>
63   using time = std::chrono::time_point<std::chrono::system_clock, Duration>;
64 
65   /**
66    * @brief The resolution of the stored date/time.
67    */
68   using usec = std::chrono::microseconds;
69 
70   /**
71    * @brief Revision kind discriminator (see @ref svn_opt_revision_kind).
72    */
73   // NOTE: Keep these values identical to those in svn_opt_revision_kind!
74   enum class kind : std::int8_t
75     {
76       unspecified = svn_opt_revision_unspecified,
77       number      = svn_opt_revision_number,
78       date        = svn_opt_revision_date,
79       committed   = svn_opt_revision_committed,
80       previous    = svn_opt_revision_previous,
81       base        = svn_opt_revision_base,
82       working     = svn_opt_revision_working,
83       head        = svn_opt_revision_head,
84     };
85 
86   /**
87    * @brief Default constructor.
88    * @post get_kind() == kind::unspecified.
89    */
revision()90   revision() noexcept
91     : tag(kind::unspecified)
92     {}
93 
94   /**
95    * @brief Construct a revision of the given kind.
96    * @pre The @a revkind argument may be any @c kind value @b except
97    *      kind::number or kind::date, which require additional
98    *      parameters and therefore have their own constructors.
99    * @post get_kind() == @a revkind.
100    * @throw std::invalid_argument if the @a revkind value
101    *        precondition is not met.
102    */
revision(kind revkind)103   explicit revision(kind revkind)
104     : tag(revkind)
105     {
106       if (revkind == kind::number || revkind == kind::date)
107         throw std::invalid_argument("invalid svn::revision::kind");
108     }
109 
110   /**
111    * @brief Construct a numbered revision.
112    * @post get_kind() == kind::number.
113    */
revision(number revnum_)114   explicit revision(number revnum_) noexcept
115     : tag(kind::number),
116       revnum(revnum_)
117     {}
118 
119   /**
120    * @brief Construct a dated revision from a system clock time point.
121    * @post get_kind() == kind::date.
122    */
123   template<typename D>
revision(time<D> time_)124   explicit revision(time<D> time_) noexcept
125     : tag(kind::date),
126       date(std::chrono::time_point_cast<usec>(time_))
127     {}
128 
129   /**
130    * @brief Assignment operator.
131    * Uses in-place destruction/construction to maintain the immutability
132    * of the revision kind.
133    */
operator =(const revision & that)134   revision& operator=(const revision& that)
135     {
136       this->~revision();
137       new(this) revision(that);
138       return *this;
139     }
140 
141   /**
142    * @brief Return the revision kind.
143    */
get_kind() const144   kind get_kind() const noexcept
145     {
146       return tag;
147     }
148 
149   /**
150    * @brief Return the revision number.
151    * @pre get_kind() == kind::number.
152    * @throw std::logic_error if the precondition is not met.
153    */
get_number() const154   number get_number() const
155     {
156       if (tag != kind::number)
157         throw std::logic_error("svn::revision kind != number");
158       return revnum;
159     }
160 
161   /**
162    * @brief Return the revision date/time as a system clock time point.
163    * @pre get_kind() == kind::date.
164    * @throw std::logic_error if the precondition is not met.
165    */
166   template<typename D>
get_date() const167   time<D> get_date() const
168     {
169       if (tag != kind::date)
170         throw std::logic_error("svn::revision kind != date");
171       return std::chrono::time_point_cast<D>(date);
172     }
173 
174 private:
175   // Even if we were using C++17, we wouldn't use std::variant because we
176   // already maintain an explicit discriminator tag for the union.
177   const kind tag;     // Union discriminator
178   union {
179     number revnum;    // (tag == kind::number): revision number.
180     time<usec> date;  // (tag == kind::date): microseconds from epoch.
181   };
182 };
183 
184 /**
185  * @related revision
186  * @brief revision::number alias for convenience.
187  */
188 using revnum = revision::number;
189 
190 /**
191  * @related revision
192  * @brief Equality comparison.
193  */
operator ==(const revision & a,const revision & b)194 inline bool operator==(const revision& a, const revision& b)
195 {
196   const auto kind = a.get_kind();
197   if (kind != b.get_kind())
198     return false;
199   else if (kind == revision::kind::number)
200     return a.get_number() == b.get_number();
201   else if (kind == revision::kind::date)
202     return a.get_date<revision::usec>() == b.get_date<revision::usec>();
203   else
204     return true;
205 }
206 
207 /**
208  * @related revision
209  * @brief Inequality comparison.
210  */
operator !=(const revision & a,const revision & b)211 inline bool operator!=(const revision& a, const revision& b)
212 {
213   return !(a == b);
214 }
215 
216 /**
217  * @related revision
218  * @brief Ordering: less-than (<tt>operator @<</tt>).
219  * @returns a @c tristate result of comparing two @c revision values,
220  * according to the following table:
221  *   <table border=1>
222  *     <tr>
223  *       <th><center><code>@<</code></center></th>
224  *       <th><center><tt>number</tt></center></th>
225  *       <th><center><tt>date</tt></center></th>
226  *       <th><center><em>other</em></center></th>
227  *     </tr>
228  *     <tr>
229  *       <th><center><tt>number</tt></center></th>
230  *       <td><center><tt>a.get_number() < b.get_number()</tt></center></td>
231  *       <td><center><em>unknown</em></center></td>
232  *       <td><center><em>unknown</em></center></td>
233  *     </tr>
234  *     <tr>
235  *       <th><center><tt>date</tt></center></th>
236  *       <td><center><em>unknown</em></center></td>
237  *       <td><center><tt>a.get_date() < b.get_date()</tt></center></td>
238  *       <td><center><em>unknown</em></center></td>
239  *     </tr>
240  *     <tr>
241  *       <th><center><em>other</em></center></th>
242  *       <td><center><em>unknown</em></center></td>
243  *       <td><center><em>unknown</em></center></td>
244  *       <td><center><em>unknown</em></center></td>
245  *     </tr>
246  *   </table>
247  */
operator <(const revision & a,const revision & b)248 inline tristate operator<(const revision& a, const revision& b)
249 {
250   const auto kind = a.get_kind();
251   if (kind != b.get_kind())
252     return tristate::unknown();
253   else if (kind == revision::kind::number)
254     return a.get_number() < b.get_number();
255   else if (kind == revision::kind::date)
256     return a.get_date<revision::usec>() < b.get_date<revision::usec>();
257   else
258     return tristate::unknown();
259 }
260 
261 /**
262  * @related revision
263  * @brief Ordering: greater-than (<tt>operator @></tt>).
264  * @returns a @c tristate result of comparing two @c revision values,
265  * according to the following table:
266  *   <table border=1>
267  *     <tr>
268  *       <th><center><code>@></code></center></th>
269  *       <th><center><tt>number</tt></center></th>
270  *       <th><center><tt>date</tt></center></th>
271  *       <th><center><em>other</em></center></th>
272  *     </tr>
273  *     <tr>
274  *       <th><center><tt>number</tt></center></th>
275  *       <td><center><tt>a.get_number() > b.get_number()</tt></center></td>
276  *       <td><center><em>unknown</em></center></td>
277  *       <td><center><em>unknown</em></center></td>
278  *     </tr>
279  *     <tr>
280  *       <th><center><tt>date</tt></center></th>
281  *       <td><center><em>unknown</em></center></td>
282  *       <td><center><tt>a.get_date() > b.get_date()</tt></center></td>
283  *       <td><center><em>unknown</em></center></td>
284  *     </tr>
285  *     <tr>
286  *       <th><center><em>other</em></center></th>
287  *       <td><center><em>unknown</em></center></td>
288  *       <td><center><em>unknown</em></center></td>
289  *       <td><center><em>unknown</em></center></td>
290  *     </tr>
291  *   </table>
292  */
operator >(const revision & a,const revision & b)293 inline tristate operator>(const revision& a, const revision& b)
294 {
295   const auto kind = a.get_kind();
296   if (kind != b.get_kind())
297     return tristate::unknown();
298   else if (kind == revision::kind::number)
299     return a.get_number() > b.get_number();
300   else if (kind == revision::kind::date)
301     return a.get_date<revision::usec>() > b.get_date<revision::usec>();
302   else
303     return tristate::unknown();
304 }
305 
306 /**
307  * @related revision
308  * @brief Ordering: less-or-equal (<tt>operator @<=</tt>).
309  * @returns a @c tristate result of comparing two @c revision values,
310  * according to the following table:
311  *   <table border=1>
312  *     <tr>
313  *       <th><center><code>@<=</code></center></th>
314  *       <th><center><tt>number</tt></center></th>
315  *       <th><center><tt>date</tt></center></th>
316  *       <th><center><em>other</em></center></th>
317  *     </tr>
318  *     <tr>
319  *       <th><center><tt>number</tt></center></th>
320  *       <td><center><tt>a.get_number() <= b.get_number()</tt></center></td>
321  *       <td><center><em>unknown</em></center></td>
322  *       <td><center><em>unknown</em></center></td>
323  *     </tr>
324  *     <tr>
325  *       <th><center><tt>date</tt></center></th>
326  *       <td><center><em>unknown</em></center></td>
327  *       <td><center><tt>a.get_date() <= b.get_date()</tt></center></td>
328  *       <td><center><em>unknown</em></center></td>
329  *     </tr>
330  *     <tr>
331  *       <th><center><em>other</em></center></th>
332  *       <td><center><em>unknown</em></center></td>
333  *       <td><center><em>unknown</em></center></td>
334  *       <td><center><em>true</em>&dagger; or <em>unknown</em></center></td>
335  *     </tr>
336  *   </table>
337  * &dagger; <em>true</em> when <tt>a.get_kind() == b.get_kind()</tt>.
338  */
operator <=(const revision & a,const revision & b)339 inline tristate operator<=(const revision& a, const revision& b)
340 {
341   return (a == b || !(a > b));
342 }
343 
344 /**
345  * @related revision
346  * @brief Ordering: greater-or-equal (<tt>operator @>=</tt>).
347  * @returns a @c tristate result of comparing two @c revision values,
348  * according to the following table:
349  *   <table border=1>
350  *     <tr>
351  *       <th><center><code>@>=</code></center></th>
352  *       <th><center><tt>number</tt></center></th>
353  *       <th><center><tt>date</tt></center></th>
354  *       <th><center><em>other</em></center></th>
355  *     </tr>
356  *     <tr>
357  *       <th><center><tt>number</tt></center></th>
358  *       <td><center><tt>a.get_number() >= b.get_number()</tt></center></td>
359  *       <td><center><em>unknown</em></center></td>
360  *       <td><center><em>unknown</em></center></td>
361  *     </tr>
362  *     <tr>
363  *       <th><center><tt>date</tt></center></th>
364  *       <td><center><em>unknown</em></center></td>
365  *       <td><center><tt>a.get_date() >= b.get_date()</tt></center></td>
366  *       <td><center><em>unknown</em></center></td>
367  *     </tr>
368  *     <tr>
369  *       <th><center><em>other</em></center></th>
370  *       <td><center><em>unknown</em></center></td>
371  *       <td><center><em>unknown</em></center></td>
372  *       <td><center><em>true</em>&dagger; or <em>unknown</em></center></td>
373  *     </tr>
374  *   </table>
375  * &dagger; <em>true</em> when <tt>a.get_kind() == b.get_kind()</tt>.
376  */
operator >=(const revision & a,const revision & b)377 inline tristate operator>=(const revision& a, const revision& b)
378 {
379   return (a == b || !(a < b));
380 }
381 
382 } // namespace svnxx
383 } // namespace subversion
384 } // namespace apache
385 
386 #endif  // SVNXX_REVISION_HPP
387