Advertisement
Guest User

Untitled

a guest
Dec 11th, 2018
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 19.91 KB | None | 0 0
  1. #ifndef GITPP_H
  2. #define GITPP_H
  3.  
  4. /* changelog
  5. *
  6. * gittp0
  7. * - initial release
  8. *
  9. * gitpp1
  10. * - add access to commit message
  11. *
  12. * gitpp2
  13. * - fix a branch iterator bug
  14. *
  15. * gitpp3
  16. * - allow iterating commits in empty repository
  17. * (this breaks the convention in other git tools...)
  18. *
  19. * gitpp4
  20. * - COMMITS::create()
  21. * - REPO constructor take path argument
  22. *
  23. * gitpp5
  24. * - remove bogus trace.h include.
  25. *
  26. * gitpp6
  27. * - allow iterating commits in a custom ref
  28. * - properly handle trace header.
  29. * - COMMIT::time_seconds
  30. * - COMMIT::show
  31. *
  32. * gitpp7
  33. * - fix inline bug that broke linking
  34. */
  35.  
  36. #include <git2/repository.h>
  37. #include <git2/annotated_commit.h>
  38. #include <git2/errors.h>
  39. #include <git2/global.h>
  40. #include <git2/revwalk.h>
  41. #include <git2/revparse.h>
  42. #include <git2/object.h>
  43. #include <git2/commit.h>
  44. #include <git2/branch.h>
  45. #include <git2/config.h>
  46. #include <git2/refs.h>
  47. #include <git2/checkout.h>
  48. #include <git2/signature.h>
  49. #include <git2/index.h>
  50.  
  51. #include <assert.h>
  52. #include <string> // std::to_string
  53. #include <vector> // std::to_string
  54.  
  55. #ifdef HAVE_TRACE_H
  56. # include "trace.h"
  57. #else
  58. #include <iostream>
  59. #define untested()
  60. #define incomplete() ( \
  61. std::cerr << "@@#\n@@@\nincomplete:" \
  62. << __FILE__ << ":" << __LINE__ << ":" << __func__ << "\n" )
  63. #define unreachable() ( \
  64. std::cerr << "@@#\n@@@\nunreachable:" \
  65. << __FILE__ << ":" << __LINE__ << ":" << __func__ << "\n" )
  66. #endif
  67.  
  68. /* -------------------------------------------------------------------------- */
  69.  
  70. // libgit2 wrapper
  71. //
  72. // iterators are one pass libgit2 does not allow normal iteration
  73.  
  74. namespace GITPP {
  75.  
  76. class EXCEPTION : public std::exception {
  77. public:
  78. explicit EXCEPTION(std::string const& s) : _what(s) { }
  79. ~EXCEPTION() noexcept {}
  80. public:
  81. virtual const char* what() const noexcept{
  82. return _what.c_str();
  83. }
  84. private:
  85. std::string _what;
  86. };
  87. class EXCEPTION_CANT_FIND : public EXCEPTION {
  88. public:
  89. explicit EXCEPTION_CANT_FIND(std::string const& s) :
  90. EXCEPTION("cant find "+ s) {}
  91. };
  92. class EXCEPTION_INVALID : public EXCEPTION {
  93. public:
  94. explicit EXCEPTION_INVALID(std::string const& s) :
  95. EXCEPTION("invalid "+ s) {}
  96. };
  97.  
  98. class SIGNATURE{
  99. public:
  100. SIGNATURE(git_commit const* c){
  101. assert(c);
  102. const git_signature *sig;
  103. if (!(sig = git_commit_author(c))) {
  104. throw EXCEPTION("something wrong in sig");
  105. }else{
  106. _s = std::make_pair(std::string(sig->name), std::string(sig->email));
  107. }
  108. }
  109. public:
  110. std::string const& name() const{ return _s.first; }
  111. std::string const& email() const{ return _s.second; }
  112. private:
  113. std::pair<std::string, std::string> _s;
  114. };
  115.  
  116.  
  117. namespace{
  118. int _do_print(git_diff_delta const*,
  119. git_diff_hunk const*,
  120. git_diff_line const* l, void* buf){
  121. std::string* b=(std::string*)(buf);
  122. *b+= std::string(1, l->origin);
  123. *b+= " " + std::string(l->content, l->content_len);
  124. return 0;
  125. }
  126. }
  127. class DIFFPRINTER{
  128.  
  129. public:
  130. DIFFPRINTER(git_diff* diff){
  131.  
  132. git_diff_format_t f=GIT_DIFF_FORMAT_PATCH;
  133. //int git_diff_print(git_diff*, git_diff_format _t, git_diff_line_cb, void*)’
  134.  
  135. buf="";
  136. git_diff_print(diff, f, &_do_print, &buf);
  137.  
  138. }
  139. std::string const& to_string(){return buf;}
  140.  
  141. private:
  142. std::string buf;
  143. };
  144.  
  145. class COMMIT{
  146. public:
  147. explicit COMMIT(git_oid i, git_repository* r): _id(i), _r(r) {
  148. if( git_commit_lookup(&_c, r, &_id) ){
  149. throw "lookup error\n";
  150. }else{
  151. }
  152. }
  153.  
  154. public:
  155. bool operator==(COMMIT const& x) const{ untested();
  156. return git_oid_equal(&_id, &x._id);
  157. }
  158. bool operator!=(COMMIT const&x){ untested();
  159. return !(*this==x);
  160. }
  161. std::string id() const{
  162. char buf[GIT_OID_HEXSZ+1];
  163. git_oid_fmt(buf, &_id);
  164. buf[GIT_OID_HEXSZ] = '\0';
  165. return std::string(buf);
  166. }
  167. std::ostream& print(std::ostream& o) const{
  168. return o << id();
  169. }
  170. std::string author() const{
  171. return signature().name();
  172. }
  173. std::string message() const {
  174. return git_commit_message(_c);
  175. }
  176. git_time_t time_seconds() const {
  177. return git_commit_time(_c);
  178. }
  179. std::string time(unsigned len=99) const {
  180. (void)len;
  181. git_time_t seconds=time_seconds();
  182.  
  183. char *a = ctime(&seconds);
  184. std::string ret(a);
  185. ret[ret.size()-1]='\0'; // chop off newline
  186. return ret;
  187. }
  188. SIGNATURE signature() const{
  189. return SIGNATURE(_c);
  190. }
  191.  
  192. std::string diff() const{
  193. incomplete();
  194. return "COMMIT::diff not implemented";
  195. }
  196. std::string show() const{
  197. int err=0;
  198. git_diff *diff;
  199. git_tree* t;
  200. git_commit* commit;
  201. git_diff_options diffopts = GIT_DIFF_OPTIONS_INIT;
  202.  
  203. if((err=git_commit_lookup(&commit, _r, &_id) < 0)){
  204. }else if((err=git_commit_tree(&t, commit)) < 0){
  205. }else{
  206. git_diff_tree_to_workdir(&diff, _r, t, &diffopts);
  207. }
  208.  
  209. if(err){
  210. throw EXCEPTION("problem in diff : "
  211. + std::string(giterr_last()->message));
  212. }else{
  213. return DIFFPRINTER(diff).to_string();
  214. }
  215. }
  216. std::string commit_message() const{
  217. return message();
  218. }
  219.  
  220. private:
  221. const git_oid _id;
  222. // const REPO& _r;
  223. git_repository* _r;
  224. git_commit* _c;
  225. }; // COMMIT
  226.  
  227. inline std::ostream& operator<< (std::ostream& o, COMMIT const& c)
  228. {
  229. return c.print(o);
  230. }
  231.  
  232. class REPO;
  233.  
  234. class CONFIG {
  235. public: // types
  236. class ITEM {
  237. public:
  238. explicit ITEM(git_config_entry* e, git_config_iterator* p, CONFIG& c)
  239. : _cfg(c), _entry(e) // , _r(c._repo)
  240. {
  241. (void)p;
  242. // if( git_config_next(&_entry, p)){
  243. // throw EXCEPTION("can't get");
  244. // }else{
  245. // }
  246. }
  247. explicit ITEM(CONFIG& p, std::string const& what)
  248. : _cfg(p) {
  249. if(/*int f=*/git_config_get_entry(&_entry, p._cfg, what.c_str())){
  250. throw EXCEPTION_CANT_FIND(what);
  251. }else{
  252. }
  253. }
  254. ITEM& operator=(const std::string& v){
  255. if(!_cfg._cfg){ untested();
  256. }else if(int error=git_config_set_string(_cfg._cfg, name().c_str(), v.c_str())){
  257. throw EXCEPTION("can't set " + std::to_string(error));
  258. }else{
  259. // ok
  260. }
  261. return *this;
  262. }
  263.  
  264. // operator std::string() const{
  265. // return std::string(_value);
  266. // }
  267. std::string value() const{
  268. assert(_entry);
  269. return _entry->value;
  270. }
  271. std::string name() const{
  272. assert(_entry);
  273. return _entry->name;
  274. }
  275. std::ostream& print(std::ostream& o) const{
  276. return o << name() << " = " << value();
  277. }
  278.  
  279. private:
  280. CONFIG& _cfg;
  281. git_config_entry* _entry;
  282. }; // ITEM
  283.  
  284. class ITER {
  285. public:
  286. ITER(ITER const& i)
  287. : _cfg(i._cfg), _i(i._i), _e(i._e)
  288. {untested();
  289. }
  290.  
  291. public:
  292. explicit ITER(CONFIG& c): _cfg(c){
  293. if( git_config_iterator_new(&_i, c._cfg)){
  294. throw EXCEPTION("iter error");
  295. }else{
  296. }
  297.  
  298. operator++();
  299. }
  300. explicit ITER(CONFIG& c, int) : _cfg(c), _i(nullptr){
  301. } // "end"
  302.  
  303. ~ITER(){
  304. git_config_iterator_free(_i);
  305. }
  306.  
  307. ITER& operator++(){
  308. assert(_i); // not end.
  309. // need to keep a pointer to entry, as we can only fetch it once (?!)
  310. int status=git_config_next(&_e, _i);
  311. if (status==GIT_ITEROVER) {
  312. _i = nullptr;
  313. _e = nullptr;
  314. assert(*this==ITER(_cfg, 0));
  315. }else if (status) {
  316. // unreachable(); // ?!
  317. _i = nullptr;
  318. _e = nullptr;
  319. assert(*this==ITER(_cfg, 0));
  320. }else{
  321. }
  322. return *this;
  323. }
  324. bool operator==(ITER const&x) const{
  325. return _i==x._i;
  326. }
  327. bool operator!=(ITER const&x){
  328. return !(*this==x);
  329. }
  330. ITEM operator*();
  331.  
  332. private:
  333. CONFIG& _cfg;
  334. git_config_iterator* _i;
  335. git_config_entry* _e;
  336. }; // ITER
  337.  
  338. public: // construct
  339. CONFIG(REPO& r);
  340. ~CONFIG(){
  341. // incomplete(); //flush here? flush always? let's see.
  342. }
  343.  
  344. public:
  345. ITEM operator[](std::string const& s){
  346. return ITEM(*this, s);
  347. }
  348. // private:
  349. ITEM create(const std::string& what){
  350. if(git_config_set_string(_cfg, what.c_str(), "notyet")){
  351. throw "incomplete";
  352. }else{
  353. }
  354. return (*this)[what];
  355. }
  356.  
  357. public: // iterate
  358. ITER begin(){
  359. return ITER(*this);
  360. }
  361. ITER end(){
  362. return ITER(*this, 0);
  363. }
  364.  
  365. private:
  366. // REPO& _repo;
  367. git_config * /*const*/ _cfg;
  368. }; // CONFIG
  369.  
  370. inline std::ostream& operator<< (std::ostream& o, CONFIG::ITEM const& c)
  371. {
  372. return c.print(o);
  373. }
  374.  
  375. class COMMITS {
  376. public: // types
  377. class COMMIT_WALKER {
  378. public:
  379. COMMIT_WALKER(COMMITS& c): _c(&c){
  380. std::fill((char*)&_id, (char*)(&_id)+sizeof(git_oid), 1);
  381. operator++();
  382. }
  383. COMMIT_WALKER(): _c(nullptr){
  384. std::fill((char*)&_id, (char*)(&_id)+sizeof(git_oid), 0);
  385. } // "end"
  386.  
  387. COMMIT_WALKER& operator++(){
  388. assert(!git_oid_iszero(&_id));
  389. if (!_c->_walk || git_revwalk_next(&_id, _c->_walk)) {
  390. std::fill((char*)&_id, (char*)(&_id)+sizeof(git_oid), 0);
  391. }else{
  392. }
  393. return *this;
  394. }
  395. bool operator==(COMMIT_WALKER const&x) const{
  396. return git_oid_equal(&_id, &x._id);
  397. }
  398. bool operator!=(COMMIT_WALKER const&x){
  399. return !(*this==x);
  400. }
  401. COMMIT operator*();
  402. private:
  403. COMMITS* _c;
  404. git_oid _id;
  405. };
  406.  
  407. public: // construct
  408. COMMITS(REPO& r, std::string const&);
  409. ~COMMITS(){
  410. git_revwalk_free(_walk);
  411. }
  412.  
  413. public: // misc
  414. COMMIT create(std::string const& message);
  415.  
  416.  
  417. public: // iterate
  418. COMMIT_WALKER begin(){
  419. return COMMIT_WALKER(*this);
  420. }
  421. COMMIT_WALKER end(){
  422. return COMMIT_WALKER();
  423. }
  424.  
  425. private:
  426. REPO& _repo;
  427. git_revwalk* _walk;
  428. }; // COMMITS
  429.  
  430. class BRANCHES;
  431.  
  432. // a git repository
  433. class REPO{
  434. public:
  435. enum create_t{
  436. _create
  437. };
  438.  
  439. public:
  440. REPO(create_t, std::string path="."){
  441. if(!git_repository_open(&_repo, path.c_str())){
  442. throw EXCEPTION("already there. doesn't work");
  443. }else if(int err=git_repository_init(&_repo, path.c_str(), 0)){
  444. throw EXCEPTION("internal error creating repo: " + std::to_string(err));
  445. }else{
  446. }
  447. }
  448.  
  449. REPO(std::string path=".") : _repo(nullptr) {
  450. git_libgit2_init();
  451.  
  452. int error=git_repository_open(&_repo, path.c_str());
  453. if (error < 0) {
  454. const git_error *e = giterr_last();
  455. throw EXCEPTION_CANT_FIND(
  456. " repository (" + std::string(e->message) + ", "
  457. + "error " + std::to_string(error) + " "
  458. + std::to_string(e->klass) + ")");
  459. }
  460.  
  461. }
  462. ~REPO(){
  463. git_repository_free(_repo);
  464. git_libgit2_shutdown();
  465. }
  466.  
  467. public:
  468. COMMITS commits(std::string const& ref="HEAD"){
  469. return COMMITS(*this, ref);
  470. }
  471. CONFIG config(){
  472. return CONFIG(*this);
  473. }
  474. BRANCHES branches();
  475. void checkout(std::string const&);
  476.  
  477. private:
  478. git_repository* _repo;
  479.  
  480. public:
  481. friend class COMMITS;
  482. friend class CONFIG;
  483. friend class BRANCHES;
  484. }; // REPO
  485.  
  486. inline COMMIT COMMITS::create(std::string const& msg)
  487. {
  488. git_signature* sig=nullptr;
  489. git_index* index=nullptr;
  490. git_oid tree_id;
  491. const git_oid* parent_id;
  492. git_oid oid;
  493. git_tree* tree=nullptr;
  494. int err=0;
  495. std::string errstring;
  496. git_repository* r(_repo._repo);
  497. char const* m=msg.c_str();
  498.  
  499. git_commit* parent=nullptr;
  500. int parents=0;
  501. git_reference* pr;
  502. if(!git_repository_head(&pr, _repo._repo)){
  503. parent_id = git_reference_target(pr);
  504. if(!git_commit_lookup(&parent, r, parent_id)){
  505. parents = 1;
  506. }else{ untested();
  507. }
  508. }else{
  509. // ignore. perhaps empty repository
  510. }
  511.  
  512.  
  513. if ((err=git_signature_default(&sig, r)) < 0) {
  514. }else if ((err=git_repository_index(&index, r)) < 0) {
  515. }else if ((err=git_index_write_tree(&tree_id, index)) < 0) {
  516. }else if ((err=git_tree_lookup(&tree, r, &tree_id)) < 0) {
  517. }else if ((err=git_commit_create_v(&oid, r, "HEAD", sig, sig,
  518. NULL, m, tree, parents, parent)) < 0)
  519. {
  520. }else{
  521. }
  522.  
  523. git_index_free(index);
  524. git_tree_free(tree);
  525. git_signature_free(sig);
  526.  
  527. if(err<0){
  528. errstring = std::string(giterr_last()->message);
  529. throw EXCEPTION(errstring + ": " + std::to_string(err));
  530. }else{
  531. return COMMIT(oid, r);
  532. }
  533. }
  534.  
  535. class BRANCH{
  536. public:
  537. explicit BRANCH(git_reference* ref):_ref(ref){}
  538.  
  539. public:
  540. std::string name() const{
  541. const char* c;
  542. git_branch_name(&c, _ref);
  543. assert(c);
  544. return std::string(c);
  545. }
  546. std::ostream& print(std::ostream& o) const{
  547. return o << name();
  548. }
  549.  
  550. //BRANCH& reset(COMMIT&){
  551. // incomplete();
  552. //}
  553.  
  554. private:
  555. git_reference* _ref;
  556. };
  557.  
  558. inline std::ostream& operator<< (std::ostream& o, BRANCH const& c)
  559. {
  560. return c.print(o);
  561. }
  562.  
  563. class BRANCHES{
  564. public:
  565. class iterator{
  566. public:
  567. explicit iterator(BRANCHES& b): _br(b), _e(nullptr){
  568. if( git_branch_iterator_new(&_i, b._repo._repo, GIT_BRANCH_LOCAL)){
  569. throw EXCEPTION("branch iter error");
  570. }else{
  571. }
  572.  
  573. operator++();
  574. }
  575. explicit iterator(BRANCHES& b, int): _i(nullptr), _br(b), _e(nullptr){
  576. }
  577. iterator(iterator const& b): _i(b._i), _br(b._br), _e(b._e){
  578. }
  579.  
  580. public:
  581. bool operator==(iterator const&x) const{
  582. return _i==x._i;
  583. }
  584. bool operator!=(iterator const&x) const{
  585. return !(*this==x);
  586. }
  587. BRANCH operator*(){
  588. return BRANCH(_e);
  589. }
  590. iterator& operator++(){
  591. assert(_i); // not end.
  592. // need to keep a pointer to entry, as we can only fetch it once (?!)
  593. int err=git_branch_next(&_e, &ot, _i);
  594. if (GIT_ITEROVER==err) {
  595. _i = nullptr;
  596. _e = nullptr;
  597. // assert(*this==iterator(_br, 0));
  598. }else if (err){
  599. throw EXCEPTION("branch iter error");
  600. // unreachable(); // ?!
  601. _i = nullptr;
  602. _e = nullptr;
  603. assert(*this==iterator(_br, 0));
  604. }else{
  605. }
  606. return *this;
  607. }
  608.  
  609. private:
  610. git_branch_iterator* _i;
  611. BRANCHES& _br;
  612. git_reference* _e;
  613. git_branch_t ot;
  614. }; // iterator
  615. public:
  616. explicit BRANCHES(REPO& r) : _repo(r)
  617. {
  618. }
  619.  
  620. public:
  621. iterator begin(){
  622. return iterator(*this);
  623. }
  624. iterator end(){
  625. return iterator(*this, 0);
  626. }
  627. BRANCH create(std::string const& name){
  628. git_reference* out;
  629. git_reference* r;
  630. if(git_repository_head(&r, _repo._repo)){ untested();
  631. throw EXCEPTION("cant obtain reference to HEAD");
  632. }else{
  633. }
  634. const git_oid* h=git_reference_target(r);
  635. git_commit* c;
  636. if(git_commit_lookup(&c, _repo._repo, h)){ untested();
  637. throw EXCEPTION("cannot lookup HEAD commit");
  638. }else{
  639. }
  640. // strdup required?
  641. if( git_branch_create( &out, _repo._repo, name.c_str(), c, 0/*force*/)){
  642. free(c);
  643. throw EXCEPTION_INVALID(name);
  644. }else{
  645. }
  646.  
  647. return BRANCH(out);
  648. }
  649. void erase(std::string const& name){
  650. git_reference* r;
  651. if( git_reference_dwim(&r, _repo._repo, name.c_str())){ untested();
  652. throw EXCEPTION_CANT_FIND(name);
  653. }else if( git_branch_delete(r)){
  654. throw EXCEPTION("error deleting branch " + name);
  655. }else{
  656. }
  657. }
  658.  
  659. // BRANCH& operator[](string const& branchname){
  660. // maybe later.
  661. // }
  662.  
  663. private:
  664. REPO& _repo;
  665. }; // BRANCHES
  666.  
  667. inline BRANCHES REPO::branches()
  668. {
  669. return BRANCHES(*this);
  670. }
  671.  
  672. inline CONFIG::CONFIG(REPO& r) // : _repo(r)
  673. {
  674. if(git_repository_config(&_cfg, r._repo)){
  675. throw EXCEPTION("can't open config for repo");
  676. }else{
  677. }
  678. }
  679. // ---------------------------------------------------------------------------- //
  680. inline COMMITS::COMMITS(REPO& r, std::string const& what="HEAD")
  681. : _repo(r)
  682. {
  683. git_revwalk_new(&_walk, _repo._repo);
  684.  
  685. int error;
  686. git_object *obj;
  687.  
  688. if ((error = git_revparse_single(&obj, _repo._repo, what.c_str())) < 0){
  689. // cannot resolve HEAD.
  690. _walk = nullptr;
  691. }else{
  692.  
  693. error = git_revwalk_push(_walk, git_object_id(obj));
  694. git_object_free(obj);
  695. if(error){ untested();
  696. throw error;
  697. }else{
  698. }
  699. }
  700. }
  701.  
  702. inline COMMIT COMMITS::COMMIT_WALKER::operator*()
  703. {
  704. return COMMIT(_id, _c->_repo._repo);
  705. }
  706.  
  707. inline CONFIG::ITEM CONFIG::ITER::operator*()
  708. {
  709. return CONFIG::ITEM(_e, _i, _cfg);
  710. }
  711.  
  712. static int resolve_refish(git_annotated_commit **commit, git_repository *repo,
  713. const char *refish)
  714. {
  715. git_reference *ref;
  716. git_object *obj;
  717. int err = 0;
  718.  
  719. assert(commit != NULL);
  720.  
  721. err = git_reference_dwim(&ref, repo, refish);
  722. assert(ref);
  723.  
  724. if (err == GIT_OK) {
  725. git_annotated_commit_from_ref(commit, repo, ref);
  726. // git_reference_free(ref); memory leak
  727. return 0;
  728. }else{
  729. }
  730.  
  731. err = git_revparse_single(&obj, repo, refish);
  732. if (err == GIT_OK) {
  733. err = git_annotated_commit_lookup(commit, repo, git_object_id(obj));
  734. // git_object_free(obj); memory leak
  735. }else{
  736. }
  737.  
  738. return err;
  739. }
  740.  
  741. //inline void REPO::checkout(COMMIT const& refname)
  742. //inline void REPO::checkout(BRANCH const& refname)
  743. inline void REPO::checkout(std::string const& refname)
  744. {
  745. git_checkout_options opts=GIT_CHECKOUT_OPTIONS_INIT;
  746. git_commit *target_commit=nullptr;
  747. git_annotated_commit *target=nullptr;
  748.  
  749. opts.checkout_strategy = GIT_CHECKOUT_SAFE;
  750.  
  751. if (resolve_refish(&target, _repo, refname.c_str())){
  752. git_annotated_commit_free(target);
  753.  
  754. throw EXCEPTION_CANT_FIND(refname);
  755. // giterr_last()->message
  756. }else{
  757. }
  758.  
  759. if(git_commit_lookup(&target_commit, _repo, git_annotated_commit_id(target))){
  760. throw EXCEPTION("cant lookup commit for " + refname);
  761. }else if(git_checkout_tree(_repo, (const git_object *)target_commit, &opts)){
  762. git_annotated_commit_free(target);
  763. throw EXCEPTION("error during checkout "+refname+": "
  764. + std::string(giterr_last()->message));
  765. }else if ( 1 ) { // } auto ref=git_annotated_commit_ref(target)) {
  766. if(git_repository_set_head(_repo, ("refs/heads/"+refname).c_str())){
  767. git_annotated_commit_free(target);
  768. throw EXCEPTION("can't update HEAD to " + refname + ": "
  769. + std::string(giterr_last()->message));
  770. }else{
  771. }
  772. }else{
  773. git_annotated_commit_free(target);
  774. }
  775. } // checkout
  776.  
  777. } // GITPP
  778.  
  779. #endif
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement