Skip to content

Commit 3df3010

Browse files
committed
Add import command.
1 parent b176dd8 commit 3df3010

File tree

8 files changed

+416
-4
lines changed

8 files changed

+416
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ COMMANDS:
4343
branch List, create, or delete branches
4444
commit Record changes
4545
daemon Starts a client
46+
import Import a repo
4647
init Create a repo
4748
log Print repo history
4849
merge Merge commits

cmd/multi/import.go

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
"os"
6+
7+
"github.com/multiverse-vcs/go-multiverse/rpc"
8+
"github.com/urfave/cli/v2"
9+
)
10+
11+
var importCommand = &cli.Command{
12+
Action: importAction,
13+
Name: "import",
14+
Usage: "Import a repo",
15+
ArgsUsage: "<name>",
16+
Flags: []cli.Flag{
17+
&cli.StringFlag{
18+
Name: "type",
19+
Aliases: []string{"t"},
20+
Usage: "Repo type",
21+
Value: "git",
22+
},
23+
&cli.StringFlag{
24+
Name: "url",
25+
Aliases: []string{"u"},
26+
Usage: "Repo url",
27+
},
28+
&cli.StringFlag{
29+
Name: "dir",
30+
Aliases: []string{"d"},
31+
Usage: "Repo directory",
32+
},
33+
},
34+
}
35+
36+
func importAction(c *cli.Context) error {
37+
if c.NArg() < 1 {
38+
cli.ShowSubcommandHelpAndExit(c, 1)
39+
}
40+
41+
name := c.Args().Get(0)
42+
43+
cwd, err := os.Getwd()
44+
if err != nil {
45+
return err
46+
}
47+
48+
if _, err := FindConfig(cwd); err == nil {
49+
return errors.New("repo already exists")
50+
}
51+
52+
client, err := rpc.NewClient()
53+
if err != nil {
54+
return err
55+
}
56+
57+
args := rpc.ImportArgs{
58+
Name: name,
59+
Type: c.String("type"),
60+
URL: c.String("url"),
61+
Dir: c.String("dir"),
62+
}
63+
64+
var reply rpc.ImportReply
65+
if err := client.Call("Service.Import", &args, &reply); err != nil {
66+
return err
67+
}
68+
69+
config := NewConfig(cwd)
70+
config.Name = name
71+
return config.Save()
72+
}

cmd/multi/main.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,15 @@ var app = &cli.App{
1212
HelpName: "multi",
1313
Usage: "Multiverse command line interface",
1414
Description: `Multiverse is a decentralized version control system for peer-to-peer software development.`,
15-
Version: "0.0.1",
15+
Version: "0.0.5",
1616
Authors: []*cli.Author{
1717
{Name: "Keenan Nemetz", Email: "[email protected]"},
1818
},
1919
Commands: []*cli.Command{
2020
branchCommand,
2121
commitCommand,
2222
daemonCommand,
23+
importCommand,
2324
initCommand,
2425
logCommand,
2526
mergeCommand,

git/git.go

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// Package git contains methods for importing Git repositories.
2+
package git
3+
4+
import (
5+
"context"
6+
"io/ioutil"
7+
8+
"github.com/go-git/go-git/v5"
9+
"github.com/go-git/go-git/v5/plumbing"
10+
"github.com/go-git/go-git/v5/plumbing/filemode"
11+
"github.com/go-git/go-git/v5/plumbing/object"
12+
"github.com/go-git/go-git/v5/storage/memory"
13+
"github.com/ipfs/go-cid"
14+
ipld "github.com/ipfs/go-ipld-format"
15+
"github.com/ipfs/go-merkledag"
16+
ufsio "github.com/ipfs/go-unixfs/io"
17+
"github.com/multiverse-vcs/go-multiverse/data"
18+
"github.com/multiverse-vcs/go-multiverse/unixfs"
19+
)
20+
21+
// DateFormat is the format to store dates in.
22+
const DateFormat = "Mon Jan 02 15:04:05 2006 -0700"
23+
24+
// importer adds objects from a git repo to a dag.
25+
type importer struct {
26+
ctx context.Context
27+
dag ipld.DAGService
28+
name string
29+
repo *git.Repository
30+
branches map[string]cid.Cid
31+
tags map[string]cid.Cid
32+
}
33+
34+
// ImportFromFS is a helper to import a git repo from a directory.
35+
func ImportFromFS(ctx context.Context, dag ipld.DAGService, name, dir string) (cid.Cid, error) {
36+
repo, err := git.PlainOpen(dir)
37+
if err != nil {
38+
return cid.Cid{}, err
39+
}
40+
41+
importer := NewImporter(ctx, dag, repo, name)
42+
return importer.AddRepository()
43+
}
44+
45+
// ImportFromURL is a helper to import a git repo from a url.
46+
func ImportFromURL(ctx context.Context, dag ipld.DAGService, name, url string) (cid.Cid, error) {
47+
opts := git.CloneOptions{
48+
URL: url,
49+
}
50+
51+
repo, err := git.Clone(memory.NewStorage(), nil, &opts)
52+
if err != nil {
53+
return cid.Cid{}, err
54+
}
55+
56+
importer := NewImporter(ctx, dag, repo, name)
57+
return importer.AddRepository()
58+
}
59+
60+
// NewImporter returns an importer for the given repo.
61+
func NewImporter(ctx context.Context, dag ipld.DAGService, repo *git.Repository, name string) *importer {
62+
return &importer{
63+
ctx: ctx,
64+
dag: dag,
65+
name: name,
66+
repo: repo,
67+
branches: make(map[string]cid.Cid),
68+
tags: make(map[string]cid.Cid),
69+
}
70+
}
71+
72+
// AddRepository adds all branches and tags to the dag.
73+
func (i *importer) AddRepository() (cid.Cid, error) {
74+
tags, err := i.repo.Tags()
75+
if err != nil {
76+
return cid.Cid{}, err
77+
}
78+
79+
branches, err := i.repo.Branches()
80+
if err != nil {
81+
return cid.Cid{}, err
82+
}
83+
84+
if err := branches.ForEach(i.AddBranch); err != nil {
85+
return cid.Cid{}, err
86+
}
87+
88+
if err := tags.ForEach(i.AddTag); err != nil {
89+
return cid.Cid{}, err
90+
}
91+
92+
mrepo := data.NewRepository(i.name)
93+
mrepo.Branches = i.branches
94+
mrepo.Tags = i.tags
95+
96+
return data.AddRepository(i.ctx, i.dag, mrepo)
97+
}
98+
99+
// AddBranch adds the branch with the given ref to the dag.
100+
func (i *importer) AddBranch(ref *plumbing.Reference) error {
101+
id, err := i.AddCommit(ref.Hash())
102+
if err != nil {
103+
return err
104+
}
105+
106+
i.branches[string(ref.Name())] = id
107+
return nil
108+
}
109+
110+
// AddTag adds the tag with the given ref to the dag.
111+
func (i *importer) AddTag(ref *plumbing.Reference) error {
112+
id, err := i.AddCommit(ref.Hash())
113+
if err != nil {
114+
return err
115+
}
116+
117+
i.tags[string(ref.Name())] = id
118+
return nil
119+
}
120+
121+
// AddCommit adds the commit with the given hash to the dag.
122+
func (i *importer) AddCommit(hash plumbing.Hash) (cid.Cid, error) {
123+
commit, err := i.repo.CommitObject(hash)
124+
if err != nil {
125+
return cid.Cid{}, err
126+
}
127+
128+
var parents []cid.Cid
129+
for _, h := range commit.ParentHashes {
130+
parent, err := i.AddCommit(h)
131+
if err != nil {
132+
return cid.Cid{}, err
133+
}
134+
135+
parents = append(parents, parent)
136+
}
137+
138+
tree, err := i.AddTree(commit.TreeHash)
139+
if err != nil {
140+
return cid.Cid{}, err
141+
}
142+
143+
mcommit := data.NewCommit(tree.Cid(), commit.Message, parents...)
144+
mcommit.Metadata["git_hash"] = hash.String()
145+
mcommit.Metadata["git_author_name"] = commit.Author.Name
146+
mcommit.Metadata["git_author_email"] = commit.Author.Email
147+
mcommit.Metadata["git_author_date"] = commit.Author.When.Format(DateFormat)
148+
mcommit.Metadata["git_committer_name"] = commit.Committer.Name
149+
mcommit.Metadata["git_committer_email"] = commit.Committer.Email
150+
mcommit.Metadata["git_committer_date"] = commit.Committer.When.Format(DateFormat)
151+
152+
return data.AddCommit(i.ctx, i.dag, mcommit)
153+
}
154+
155+
// AddTree adds the tree with the given hash to the dag.
156+
func (i *importer) AddTree(hash plumbing.Hash) (ipld.Node, error) {
157+
tree, err := i.repo.TreeObject(hash)
158+
if err != nil {
159+
return nil, err
160+
}
161+
162+
dir := ufsio.NewDirectory(i.dag)
163+
for _, entry := range tree.Entries {
164+
subnode, err := i.AddTreeEntry(entry)
165+
if err != nil {
166+
return nil, err
167+
}
168+
169+
if err := dir.AddChild(i.ctx, entry.Name, subnode); err != nil {
170+
return nil, err
171+
}
172+
}
173+
174+
node, err := dir.GetNode()
175+
if err != nil {
176+
return nil, err
177+
}
178+
179+
if err := i.dag.Add(i.ctx, node); err != nil {
180+
return nil, err
181+
}
182+
183+
return node, nil
184+
}
185+
186+
// AddTreeEntry adds the tree entry with the given hash to the dag.
187+
func (i *importer) AddTreeEntry(entry object.TreeEntry) (ipld.Node, error) {
188+
if entry.Mode == filemode.Dir {
189+
return i.AddTree(entry.Hash)
190+
}
191+
192+
blob, err := i.repo.BlobObject(entry.Hash)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
r, err := blob.Reader()
198+
if err != nil {
199+
return nil, err
200+
}
201+
defer r.Close()
202+
203+
if entry.Mode.IsFile() {
204+
return unixfs.Chunk(i.ctx, i.dag, r)
205+
}
206+
207+
target, err := ioutil.ReadAll(r)
208+
if err != nil {
209+
return nil, err
210+
}
211+
212+
node := merkledag.NodeWithData(target)
213+
if err := i.dag.Add(i.ctx, node); err != nil {
214+
return nil, err
215+
}
216+
217+
return node, nil
218+
}

git/git_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package git
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/ipfs/go-merkledag/dagutils"
8+
"github.com/multiverse-vcs/go-multiverse/data"
9+
)
10+
11+
func TestImportFromURL(t *testing.T) {
12+
ctx := context.Background()
13+
dag := dagutils.NewMemoryDagService()
14+
15+
id, err := ImportFromURL(ctx, dag, "go-multiverse", "https://github.com/multiverse-vcs/go-multiverse")
16+
if err != nil {
17+
t.Fatal("failed to import git repo")
18+
}
19+
20+
repo, err := data.GetRepository(ctx, dag, id)
21+
if err != nil {
22+
t.Fatal("failed to get repo")
23+
}
24+
25+
if repo.Name != "go-multiverse" {
26+
t.Error("unexpected repo name")
27+
}
28+
}

go.mod

+1-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ go 1.16
44

55
require (
66
github.com/alecthomas/chroma v0.8.2
7+
github.com/go-git/go-git/v5 v5.2.0
78
github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 // indirect
89
github.com/ipfs/go-bitswap v0.3.2
910
github.com/ipfs/go-blockservice v0.1.4
@@ -22,7 +23,6 @@ require (
2223
github.com/ipfs/go-path v0.0.8
2324
github.com/ipfs/go-unixfs v0.2.4
2425
github.com/julienschmidt/httprouter v1.3.0
25-
github.com/kr/text v0.2.0 // indirect
2626
github.com/libp2p/go-libp2p v0.12.0
2727
github.com/libp2p/go-libp2p-connmgr v0.2.4
2828
github.com/libp2p/go-libp2p-core v0.7.0
@@ -32,13 +32,11 @@ require (
3232
github.com/multiformats/go-multihash v0.0.14
3333
github.com/nasdf/diff3 v0.0.1
3434
github.com/nasdf/ulimit v0.0.1
35-
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
3635
github.com/onsi/ginkgo v1.14.0 // indirect
3736
github.com/polydawn/refmt v0.0.0-20190807091052-3d65705ee9f1
3837
github.com/urfave/cli/v2 v2.3.0
3938
github.com/yuin/goldmark v1.3.1
4039
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a // indirect
4140
golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect
4241
golang.org/x/text v0.3.3 // indirect
43-
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
4442
)

0 commit comments

Comments
 (0)