summaryrefslogtreecommitdiffstats
path: root/lib/utils/git.js
blob: 6884b835873fe0373591d7bec16650bbb558e4b5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
var is = require('is');
var path = require('path');
var crc = require('crc');
var URI = require('urijs');

var pathUtil = require('./path');
var Promise = require('./promise');
var command = require('./command');
var fs = require('./fs');

var GIT_PREFIX = 'git+';

function Git() {
    this.tmpDir;
    this.cloned = {};
}

// Return an unique ID for a combinaison host/ref
Git.prototype.repoID = function(host, ref) {
    return crc.crc32(host+'#'+(ref || '')).toString(16);
};

// Allocate a temporary folder for cloning repos in it
Git.prototype.allocateDir = function() {
    var that = this;

    if (this.tmpDir) return Promise();

    return fs.tmpDir()
    .then(function(dir) {
        that.tmpDir = dir;
    });
};

// Clone a git repository if non existant
Git.prototype.clone = function(host, ref) {
    var that = this;

    return this.allocateDir()

    // Return or clone the git repo
    .then(function() {
        // Unique ID for repo/ref combinaison
        var repoId = that.repoID(host, ref);

        // Absolute path to the folder
        var repoPath = path.join(that.tmpDir, repoId);

        if (that.cloned[repoId]) return repoPath;

        // Clone repo
        return command.exec('git clone '+host+' '+repoPath)

        // Checkout reference if specified
        .then(function() {
            that.cloned[repoId] = true;

            if (!ref) return;
            return command.exec('git checkout '+ref, { cwd: repoPath });
        })
        .thenResolve(repoPath);
    });
};

// Get file from a git repo
Git.prototype.resolve = function(giturl) {
    // Path to a file in a git repo?
    if (!Git.isUrl(giturl)) {
        if (this.resolveRoot(giturl)) return Promise(giturl);
        return Promise(null);
    }
    if (is.string(giturl)) giturl = Git.parseUrl(giturl);
    if (!giturl) return Promise(null);

    // Clone or get from cache
    return this.clone(giturl.host, giturl.ref)
    .then(function(repo) {
        return path.resolve(repo, giturl.filepath);
    });
};

// Return root of git repo from a filepath
Git.prototype.resolveRoot = function(filepath) {
    var relativeToGit, repoId;

    // No git repo cloned, or file is not in a git repository
    if (!this.tmpDir || !pathUtil.isInRoot(this.tmpDir, filepath)) return null;

    // Extract first directory (is the repo id)
    relativeToGit = path.relative(this.tmpDir, filepath);
    repoId = relativeToGit.split(path.sep)[0];
    if (!repoId) {
        return;
    }

    // Return an absolute file
    return path.resolve(this.tmpDir, repoId);
};

// Check if an url is a git dependency url
Git.isUrl = function(giturl) {
    return (giturl.indexOf(GIT_PREFIX) === 0);
};

// Parse and extract infos
Git.parseUrl = function(giturl) {
    var ref, uri, fileParts, filepath;

    if (!Git.isUrl(giturl)) return null;
    giturl = giturl.slice(GIT_PREFIX.length);

    uri = new URI(giturl);
    ref = uri.fragment() || null;
    uri.fragment(null);

    // Extract file inside the repo (after the .git)
    fileParts = uri.path().split('.git');
    filepath = fileParts.length > 1? fileParts.slice(1).join('.git') : '';
    if (filepath[0] == '/') {
        filepath = filepath.slice(1);
    }

    // Recreate pathname without the real filename
    uri.path(fileParts[0] + '.git');

    return {
        host: uri.toString(),
        ref: ref,
        filepath: filepath
    };
};

module.exports = Git;