monotone

monotone Mtn Source Tree

Root/git_import.cc

1// -*- mode: C++; c-file-style: "gnu"; indent-tabs-mode: nil -*-
2// vim:sw=2:
3// Copyright (C) 2005 Petr Baudis <pasky@suse.cz>
4// all rights reserved.
5// licensed to the public under the terms of the GNU GPL (>= 2)
6// see the file COPYING for details
7
8// Based on cvs_import by graydon hoare <graydon@pobox.com>
9// Sponsored by Google's Summer of Code and SuSE
10
11#include <algorithm>
12#include <iostream>
13#include <fstream>
14#include <iterator>
15#include <list>
16#include <map>
17#include <set>
18#include <sstream>
19#include <stack>
20#include <stdexcept>
21#include <string>
22#include <vector>
23#include <queue>
24#include <stdlib.h>
25
26#ifndef WIN32
27
28#include <unistd.h>
29
30#include <stdio.h>
31#include <string.h> // strdup(), woo-hoo!
32
33#include <boost/shared_ptr.hpp>
34#include <boost/scoped_ptr.hpp>
35#include <boost/lexical_cast.hpp>
36#include <boost/tokenizer.hpp>
37
38#include <boost/filesystem/path.hpp>
39#include <boost/filesystem/operations.hpp>
40#include <boost/filesystem/convenience.hpp>
41
42#include "app_state.hh"
43#include "cert.hh"
44#include "constants.hh"
45#include "database.hh"
46#include "file_io.hh"
47#include "git.hh"
48#include "git_import.hh"
49#include "keys.hh"
50#include "manifest.hh"
51#include "mkstemp.hh"
52#include "packet.hh"
53#include "sanity.hh"
54#include "transforms.hh"
55#include "ui.hh"
56
57using namespace std;
58using boost::shared_ptr;
59using boost::scoped_ptr;
60
61/* How do we import the history?
62 *
63 * The GIT history is a DAG, each commit contains list of zero or more parents.
64 * At the start, we know the "head" commit ID, but in order to reconstruct
65 * the history in monotone, we have to start from the root commit ID and
66 * traverse to its children.
67 *
68 * The approach we take is to take the head, and get topologically sorted DAG
69 * of its ancestry, with the roots at the top of the stack. Then, we take the
70 * revisions and convert them one-by-one. To translate the parents properly,
71 * we keep a git_id->monotone_id hashmap.
72 *
73 * The alternative approach would be to do the topological sort on our own and
74 * while doing it also make a reversed connectivity graph, with each commit
75 * associated with its children. That should be faster and you wouldn't need
76 * the hash, but it wouldn't be as easy to code, so it's a TODO.
77 */
78
79struct git_history;
80
81struct
82git_db
83{
84 const system_path path;
85
86 void get_object(const string type, const git_object_id objid, filebuf &fb);
87 void get_object(const string type, const git_object_id objid, data &dat);
88 string get_object_type(const git_object_id objid);
89
90 // DAG of the revision ancestry in topological order
91 // (top of the stack are the earliest revisions)
92 // The @revision can be even head name.
93 stack<git_object_id> load_revs(const string revision, const set<git_object_id> &exclude);
94
95 git_db(const system_path &path_) : path(path_) { }
96};
97
98
99struct
100git_history
101{
102 git_db db;
103
104 git_mt_commitmap commitmap;
105 map<git_object_id, file_id> filemap;
106
107 ticker n_revs;
108 ticker n_objs;
109
110 string branch;
111
112 git_history(const system_path & path);
113};
114
115
116
117/*** The raw GIT interface */
118
119void
120git_db::get_object(const string type, const git_object_id objid, filebuf &fb)
121{
122 capture_git_cmd_output(F("git-cat-file %s %s") % type % objid(), fb);
123}
124
125void
126git_db::get_object(const string type, const git_object_id objid, data &dat)
127{
128 filebuf fb;
129 get_object(type, objid, fb);
130 istream stream(&fb);
131
132 Botan::Pipe pipe;
133 pipe.start_msg();
134 stream >> pipe;
135 pipe.end_msg();
136 dat = pipe.read_all_as_string();
137}
138
139string
140git_db::get_object_type(const git_object_id objid)
141{
142 filebuf fb;
143 capture_git_cmd_output(F("git-cat-file -t %s") % objid(), fb);
144 istream stream(&fb);
145
146 string type;
147 stream >> type;
148 return type;
149}
150
151
152stack<git_object_id>
153git_db::load_revs(const string revision, const set<git_object_id> &exclude)
154{
155 string excludestr;
156 for (set<git_object_id>::const_iterator i = exclude.begin();
157 i != exclude.end(); ++i)
158 excludestr += " \"^" + (*i)() + "\"";
159
160 filebuf fb;
161 capture_git_cmd_output(F("git-rev-list --topo-order %s %s")
162 % revision % excludestr, fb);
163 istream stream(&fb);
164
165 stack<git_object_id> st;
166 while (!stream.eof())
167 {
168 char revbuf[41];
169 stream.getline(revbuf, 41);
170 if (strlen(revbuf) < 40)
171 continue;
172 L(F("noted revision %s") % revbuf);
173 st.push(git_object_id(string(revbuf)));
174 }
175 L(F("Loaded all revisions"));
176 return st;
177}
178
179
180
181
182/*** The GIT importer itself */
183
184static file_id
185import_git_blob(git_history &git, app_state &app, git_object_id gitbid)
186{
187 L(F("Importing blob '%s'") % gitbid());
188 map<git_object_id, file_id>::const_iterator i = git.filemap.find(gitbid);
189 if (i != git.filemap.end())
190 {
191 L(F(" -> map hit '%s'") % i->second);
192 return i->second;
193 }
194
195 data dat;
196 git.db.get_object("blob", gitbid, dat);
197 file_id fid;
198 calculate_ident(dat, fid);
199
200 if (! app.db.file_version_exists(fid))
201 {
202 app.db.put_file(fid, dat);
203 }
204 git.filemap[gitbid()] = fid;
205 ++git.n_objs;
206 return fid;
207}
208
209static void
210import_git_tree(git_history &git, app_state &app, git_object_id gittid,
211 manifest_map &manifest, string prefix, attr_map &attrs)
212{
213 L(F("Importing tree '%s'") % gittid());
214
215 data dat;
216 git.db.get_object("tree", gittid, dat);
217
218 unsigned pos = 0;
219 while (pos < dat().length())
220 {
221 /* "mode name\0hash" */
222 int infoend = dat().find('\0', pos);
223 istringstream str(dat().substr(pos, infoend - pos));
224 int mode;
225 string name;
226 str >> oct >> mode;
227 str >> name;
228 L(F("tree entry %o '%s' (%d)") % mode % name % (infoend - pos));
229 pos = infoend + 1;
230
231 string rawid = dat().substr(pos, 20);
232 git_object_id gitoid(encode_hexenc(rawid));
233 L(F(" [%s]") % gitoid());
234 pos += 20;
235
236 string fullname(prefix + name);
237
238 if (mode & 040000) // directory
239 import_git_tree(git, app, gitoid, manifest, fullname + '/', attrs);
240 else
241 {
242 file_path fpath = file_path_internal(fullname);
243
244 if (mode & 0100) // executable
245 {
246 L(F("marking '%s' as executable") % fullname);
247 attrs[fpath]["execute"] = "true";
248 }
249
250 file_id fid = import_git_blob(git, app, gitoid);
251 L(F("entry monoid [%s]") % fid.inner());
252 manifest.insert(manifest_entry(fpath, fid));
253 }
254 }
255
256 ++git.n_objs;
257}
258
259
260// TODO: Make git_heads_on_branch() and historical_gitrev_to_monorev() share
261// code.
262
263// Get the list of GIT heads in the database.
264// Under some circumstances, it might insert some redundant items into the set
265// (which doesn't matter for our current usage).
266static void
267git_heads_on_branch(git_history &git, app_state &app, set<git_object_id> &git_heads)
268{
269 queue<revision_id> frontier;
270 set<revision_id> seen;
271
272 // Take only heads in our branch - even if the commits are already in the db,
273 // we want to import them again, just to add our branch membership to them.
274 // (TODO)
275 set<revision_id> heads;
276 get_branch_heads(git.branch, app, heads);
277 for (set<revision_id>::const_iterator i = heads.begin();
278 i != heads.end(); ++i)
279 frontier.push(*i);
280
281 while (!frontier.empty())
282 {
283 revision_id rid = frontier.front(); frontier.pop();
284
285 if (seen.find(rid) != seen.end())
286 continue;
287 seen.insert(rid);
288
289 revision_set rev;
290 app.db.get_revision(rid, rev);
291
292 vector<revision<cert> > certs;
293 app.db.get_revision_certs(rid, gitcommit_id_cert_name, certs);
294 for (vector<revision<cert> >::const_iterator c = certs.begin();
295 c != certs.end(); c++)
296 {
297 // This is a GIT commit, then.
298 cert_value cv;
299 decode_base64(c->inner().value, cv);
300 git_object_id gitrid = cv();
301
302 git.commitmap[gitrid()] = make_pair(rid, rev.new_manifest);
303
304 git_heads.insert(gitrid);
305 continue; // stop traversing in this direction
306 }
307
308 for (edge_map::const_iterator e = rev.edges.begin();
309 e != rev.edges.end(); ++e)
310 {
311 frontier.push(edge_old_revision(e));
312 }
313 }
314}
315
316// extract_path_set() is silly and wipes its playground first
317static void
318extract_path_set_cont(manifest_map const & man, path_set & paths)
319{
320 for (manifest_map::const_iterator i = man.begin();
321 i != man.end(); ++i)
322 paths.insert(manifest_entry_path(i));
323}
324
325static file_id null_ident;
326
327// complete_change_set() does not work for file additions/removals,
328// so let's do it ourselves. We need nothing of this funky analysis
329// stuff since we support no renames.
330static void
331full_change_set(manifest_map const & m_old,
332 manifest_map const & m_new,
333 change_set & cs)
334{
335 set<file_path> paths;
336 extract_path_set_cont(m_old, paths);
337 extract_path_set_cont(m_new, paths);
338
339 for (set<file_path>::const_iterator i = paths.begin();
340 i != paths.end(); ++i)
341 {
342 manifest_map::const_iterator j = m_old.find(*i);
343 manifest_map::const_iterator k = m_new.find(*i);
344 L(F("full_change_set: looking up '%s' - hits old %d and new %d")
345% *i % (j != m_old.end()) % (k != m_new.end()));
346 if (j == m_old.end())
347{
348 L(F("full_change_set: adding %s") % manifest_entry_id(k));
349 cs.add_file(*i, manifest_entry_id(k));
350}
351 else if (k == m_new.end())
352{
353 L(F("full_change_set: deleting %s") % manifest_entry_id(j));
354 cs.delete_file(*i);
355}
356 else if (!(manifest_entry_id(j) == manifest_entry_id(k)))
357{
358 L(F("full_change_set: delta %s -> %s") % manifest_entry_id(j) % manifest_entry_id(k));
359 cs.deltas.insert(std::make_pair(*i, std::make_pair(manifest_entry_id(j),
360 manifest_entry_id(k))));
361}
362 }
363}
364
365static void
366parse_person_line(string &line, git_person &person, time_t &time)
367{
368 int emailstart = line.find('<');
369 int emailend = line.find('>', emailstart);
370 int timeend = line.find(' ', emailend + 2);
371 person.name = line.substr(0, emailstart - 1);
372 person.email = line.substr(emailstart + 1, emailend - emailstart - 1);
373 time = atol(line.substr(emailend + 2, timeend - emailend - 2).c_str());
374 L(F("Person name: '%s', email: '%s', time: '%d'")
375 % person.name % person.email % time);
376}
377
378static void
379resolve_commit(git_history &git, app_state &app, git_object_id gitcid, revision_id &rev, manifest_id &mid)
380{
381 // given the topo order, we ought to have the parent hashed - except
382 // for incremental imports
383 map<git_object_id, pair<revision_id, manifest_id>
384 >::const_iterator cm = git.commitmap.find(gitcid());
385 if (cm != git.commitmap.end())
386 {
387 rev = cm->second.first;
388 mid = cm->second.second;
389 }
390 else
391 {
392 historical_gitrev_to_monorev(git.branch, &git.commitmap, app, gitcid(), rev);
393 app.db.get_revision_manifest(rev, mid);
394 }
395}
396
397static revision_id
398import_git_commit(git_history &git, app_state &app, git_object_id gitrid)
399{
400 L(F("Importing commit '%s'") % gitrid());
401 filebuf fb;
402 git.db.get_object("commit", gitrid, fb);
403 istream stream(&fb);
404
405 bool header = true;
406 revision_set rev;
407 edge_map edges;
408 vector<git_object_id> parents;
409
410 manifest_map manifest;
411 // XXX: it might be user policy decision whether to take author
412 // or committer as monotone author
413 git_person author;
414 time_t author_time = 0;
415 git_person committer;
416 time_t commit_time = 0;
417 string logmsg;
418
419 // Read until eof - we have to peek() first since eof is set only after
420 // a read _after_ the end of the file, so we would get one superfluous
421 // iteration introducing trailing empty line (from failed getline()).
422 while (!(stream.peek(), stream.eof()))
423 {
424 // XXX: Allow arbitrarily long lines
425 char linebuf[256];
426 stream.getline(linebuf, 256);
427 string line = linebuf;
428
429 if (header && line.size() == 0)
430 {
431 header = false;
432 continue;
433 }
434
435 if (!header)
436 {
437 L(F("LOG: %s") % line);
438 logmsg += line + '\n';
439 continue;
440 }
441
442 // HEADER
443 // The order is always: tree, parent, author, committer
444 // Parent may be present zero times or more, all the other items
445 // are always present exactly one time.
446
447 string keyword = line.substr(0, line.find(' '));
448 string param = line.substr(line.find(' ') + 1);
449
450 L(F("HDR: '%s' => '%s'") % keyword % param);
451 if (keyword == "tree")
452 {
453 attr_map attrs;
454 import_git_tree(git, app, param, manifest, "", attrs);
455
456 // Write the attribute map
457 {
458 data attr_data;
459 write_attr_map(attr_data, attrs);
460
461 file_id fid;
462 calculate_ident(attr_data, fid);
463 if (! app.db.file_version_exists(fid))
464 app.db.put_file(fid, attr_data);
465
466 file_path attr_path;
467 get_attr_path(attr_path);
468 manifest.insert(manifest_entry(attr_path, fid));
469 }
470
471 calculate_ident(manifest, rev.new_manifest);
472 if (! app.db.manifest_version_exists(rev.new_manifest))
473 {
474 manifest_data manidata;
475 write_manifest_map(manifest, manidata);
476 // TODO: put_manifest_with_delta()
477 app.db.put_manifest(rev.new_manifest, manidata);
478 }
479
480 L(F("[%s] Manifest ID: '%s'") % gitrid() % rev.new_manifest.inner());
481 }
482 else if (keyword == "parent")
483 {
484 parents.push_back(param);
485 }
486 else if (keyword == "committer")
487 {
488 parse_person_line(param, committer, commit_time);
489 }
490 else if (keyword == "author")
491 {
492 parse_person_line(param, author, author_time);
493 }
494 }
495
496
497 // Add edges to parents:
498
499 for (unsigned i = 0; i < parents.size(); i++)
500 {
501 revision_id parent_rev;
502 manifest_id parent_mid;
503
504 resolve_commit(git, app, parents[i], parent_rev, parent_mid);
505
506 L(F("parent revision '%s'") % parent_rev.inner());
507 L(F("parent manifest '%s', loading...") % parent_mid.inner());
508
509 boost::shared_ptr<change_set> changes(new change_set());
510
511 if (parents.size() == 2)
512{
513 // A merge. See the huge comment in
514 // anc_graph::construct_revision_from_ancestry() if you want to
515 // know why are we bothering with this stuff.
516 L(F("anti-suture protection..."));
517 revision_id other_parent_rev;
518 manifest_id other_parent_mid;
519 resolve_commit(git, app, parents[unsigned(1 - i)], other_parent_rev, other_parent_mid);
520 change_set_for_merge(app, parent_rev, other_parent_rev, parent_mid, rev.new_manifest, *changes);
521
522} else {
523 manifest_map parent_man;
524 app.db.get_manifest(parent_mid, parent_man);
525 // Nothing to see here, please move along.
526 // complete_change_set(parent_man, manifest, *changes);
527 full_change_set(parent_man, manifest, *changes);
528}
529
530 {
531data cset;
532write_change_set(*changes, cset);
533L(F("Changeset:\n%s") % cset());
534 }
535
536 edges.insert(make_pair(parent_rev, make_pair(parent_mid, changes)));
537 }
538
539
540 // Connect with the ancestry:
541
542 edge_map::const_iterator e = edges.begin();
543 // In the normal case, edges will have only single member.
544 if (e != edges.end()) // Root commit has no parents!
545 rev.edges.insert(*(e++));
546
547 // For regular merges, it will have two members.
548 if (e != edges.end())
549 rev.edges.insert(*(e++));
550
551 revision_id rid;
552 bool put_commit = true;
553 // But for octopus merges, it will have even more. That's why are
554 // we doing all this funny iteration stuff.
555 bool octopus = false;
556
557 while (put_commit)
558 {
559 calculate_ident(rev, rid);
560 L(F("[%s] Monotone commit ID: '%s'") % gitrid() % rid.inner());
561 if (! app.db.revision_exists(rid))
562app.db.put_revision(rid, rev);
563 git.commitmap[gitrid()] = make_pair(rid, rev.new_manifest);
564 ++git.n_revs;
565 ++git.n_objs;
566
567 packet_db_writer dbw(app);
568 cert_revision_in_branch(rid, cert_value(git.branch), app, dbw);
569 cert_revision_author(rid, author.name, app, dbw);
570 cert_revision_date_time(rid, commit_time, app, dbw);
571 if (octopus)
572cert_revision_changelog(rid,
573"Dummy commit representing GIT octopus merge.\n(See the previous commit.)",
574app, dbw);
575 else
576cert_revision_changelog(rid, logmsg, app, dbw);
577
578 put_simple_revision_cert(rid, gitcommit_id_cert_name,
579 gitrid(), app, dbw);
580 string ctercert = committer.name + " <" + committer.email + "> "
581+ boost::lexical_cast<string>(commit_time);
582 put_simple_revision_cert(rid, gitcommit_committer_cert_name,
583 ctercert, app, dbw);
584
585 put_commit = false;
586 if (e != edges.end())
587 {
588 L(F("OCTOPUS MERGE"));
589 // Octopus merge - keep going.
590 put_commit = true;
591 octopus = true;
592
593 rev.edges.clear();
594 rev.edges.insert(*(e++));
595 // The current commit. Manifest stays the same so we needn't
596 // bother with changeset.
597 rev.edges.insert(make_pair(rid, make_pair(rev.new_manifest,
598 boost::shared_ptr<change_set>(new change_set())
599)));
600}
601 }
602
603 return rid;
604}
605
606class
607heads_tree_walker
608 : public absolute_tree_walker
609{
610 git_history & git;
611 app_state & app;
612public:
613 heads_tree_walker(git_history & g, app_state & a)
614 : git(g), app(a)
615 {
616 }
617 virtual void visit_file(system_path const & path)
618 {
619 L(F("Processing head file '%s'") % path);
620
621 data refdata;
622 read_data(path, refdata);
623
624 // We can't just .leaf() - there can be heads like "net/ipv4" and such.
625 // XXX: My head hurts from all those temporary variables.
626 system_path spheadsdir = git.db.path / "refs/heads";
627 fs::path headsdir(spheadsdir.as_external(), fs::native);
628 fs::path headpath(path.as_external(), fs::native);
629 std::string strheadpath = headpath.string(), strheadsdir = headsdir.string();
630
631 N(strheadpath.substr(0, strheadsdir.length()) == strheadsdir,
632 F("heads directory name screwed up - %s does not begin with %s")
633 % strheadpath % strheadsdir);
634 std::string headname(strheadpath.substr(strheadsdir.length() + 1)); // + '/'
635
636 git.branch = app.branch_name();
637 if (headname != "master")
638 git.branch += "." + headname;
639
640 set<git_object_id> revs_exclude;
641 git_heads_on_branch(git, app, revs_exclude);
642 stack<git_object_id> revs = git.db.load_revs(headname, revs_exclude);
643
644 while (!revs.empty())
645 {
646 ui.set_tick_trailer(revs.top()());
647 import_git_commit(git, app, revs.top());
648 revs.pop();
649 }
650 ui.set_tick_trailer("");
651 }
652 virtual ~heads_tree_walker() {}
653};
654
655
656static void
657import_git_tag(git_history &git, app_state &app, git_object_id gittid,
658 git_object_id &targetobj)
659{
660 L(F("Importing tag '%s'") % gittid());
661 data dat;
662 git.db.get_object("tag", gittid, dat);
663 string str(dat());
664
665 // The tag object header always starts with an "object" line which is the
666 // only thing interesting for us.
667
668 str.erase(0, str.find(' ') + 1);
669 str.erase(str.find('\n'));
670 I(str.length() == 40);
671 targetobj = str;
672}
673
674static bool
675resolve_git_tag(git_history &git, app_state &app, string &name,
676 git_object_id &gitoid, revision_id &rev)
677{
678 // The cheapest first:
679 map<git_object_id, pair<revision_id, manifest_id> >::const_iterator i = git.commitmap.find(gitoid());
680 if (i != git.commitmap.end())
681 {
682 L(F("commitmap hit '%s'") % i->second.first.inner());
683 rev = i->second.first;
684 return true;
685 }
686
687 // Here, we could check the other maps and throw an error, but since tags
688 // of other objects than tags are extremely rare, it's really not worth it.
689
690 // To avoid potentially scanning all the history, check if it's a tag object
691 // (very common), or indeed a "strange" one:
692 string type = git.db.get_object_type(gitoid);
693
694 if (type == "tag")
695 {
696 git_object_id obj;
697 import_git_tag(git, app, gitoid, obj);
698 return resolve_git_tag(git, app, name, obj, rev);
699 }
700 else if (type == "commit")
701 {
702 historical_gitrev_to_monorev(git.branch, &git.commitmap, app, gitoid, rev);
703 return true;
704 }
705 else
706 {
707 ui.warn(F("Warning: GIT tag '%s' (%s) does not tag a revision but a %s. Skipping...")
708 % name % gitoid() % type);
709 return false;
710 }
711}
712
713static void
714import_unresolved_git_tag(git_history &git, app_state &app, string name, git_object_id gitoid)
715{
716 L(F("Importing tag '%s' -> '%s'") % name % gitoid());
717
718 // Does the tag already exist?
719 // FIXME: Just look it up in the db.
720 vector< revision<cert> > certs;
721 app.db.get_revision_certs(tag_cert_name, certs);
722 for (vector< revision<cert> >::const_iterator i = certs.begin();
723 i != certs.end(); ++i)
724 {
725 cert_value cname;
726 cert c = i->inner();
727 decode_base64(c.value, cname);
728 if (cname == name)
729 {
730 L(F("tag already exists"));
731 return;
732 }
733 }
734
735 revision_id rev;
736 if (!resolve_git_tag(git, app, name, gitoid, rev))
737 return;
738
739 L(F("Writing tag '%s' -> '%s'") % name % rev.inner());
740 packet_db_writer dbw(app);
741 cert_revision_tag(rev.inner(), name, app, dbw);
742}
743
744class
745tags_tree_walker
746 : public absolute_tree_walker
747{
748 git_history & git;
749 app_state & app;
750public:
751 tags_tree_walker(git_history & g, app_state & a)
752 : git(g), app(a)
753 {
754 }
755 virtual void visit_file(system_path const & path)
756 {
757 L(F("Processing tag file '%s'") % path);
758
759 data refdata;
760 read_data(path, refdata);
761
762 // We can't just .leaf() - there can be tags like "net/v1.0" and such.
763 // XXX: My head hurts from all those temporary variables.
764 system_path sptagsdir = git.db.path / "refs/tags";
765 fs::path tagsdir(sptagsdir.as_external(), fs::native);
766 fs::path tagpath(path.as_external(), fs::native);
767 std::string strtagpath = tagpath.string(), strtagsdir = tagsdir.string();
768
769 N(strtagpath.substr(0, strtagsdir.length()) == strtagsdir,
770 F("tags directory name screwed up - %s does not being with %s")
771 % strtagpath % strtagsdir);
772 std::string tagname(strtagpath.substr(strtagsdir.length() + 1)); // + '/'
773
774 import_unresolved_git_tag(git, app, tagname, refdata().substr(0, 40));
775 }
776 virtual ~tags_tree_walker() {}
777};
778
779
780git_history::git_history(system_path const & path)
781 : db(path), n_revs("revisions", "r", 1), n_objs("objects", "o", 10)
782{
783}
784
785
786void
787import_git_repo(system_path const & gitrepo,
788 app_state & app)
789{
790 {
791 // early short-circuit to avoid failure after lots of work
792 rsa_keypair_id key;
793 N(guess_default_key(key,app),
794 F("no unique private key for cert construction"));
795 require_password(key, app);
796 }
797
798 require_path_is_directory(gitrepo,
799 F("repo %s does not exist") % gitrepo,
800 F("repo %s is not a directory") % gitrepo);
801
802 {
803 char * env_entry = strdup((string("GIT_DIR=") + gitrepo.as_external()).c_str());
804 putenv(env_entry);
805 }
806
807 N(app.branch_name() != "", F("need base --branch argument for importing"));
808
809 git_history git(gitrepo);
810
811 {
812 system_path heads_tree = gitrepo / "refs/heads";
813 N(directory_exists(heads_tree),
814 F("path %s is not a directory") % heads_tree);
815
816 transaction_guard guard(app.db);
817 app.db.ensure_open();
818
819 heads_tree_walker walker(git, app);
820 walk_tree_absolute(heads_tree, walker);
821 guard.commit();
822 }
823
824 system_path tags_tree = gitrepo / "refs/tags";
825 if (path_exists(tags_tree))
826 {
827 N(directory_exists(tags_tree),
828 F("path %s is not a directory") % tags_tree);
829
830 transaction_guard guard(app.db);
831 app.db.ensure_open();
832
833 tags_tree_walker walker(git, app);
834 walk_tree_absolute(tags_tree, walker);
835 guard.commit();
836 }
837
838 return;
839}
840
841
842#else // WIN32
843
844void
845import_git_repo(system_path const & gitrepo,
846 app_state & app)
847{
848 E("git import not supported on win32");
849}
850
851#endif

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status