summaryrefslogtreecommitdiffstats
path: root/lib/utils/git.js
blob: 72c8818267a963166dc329626465ca02ec543f63 (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
var  Q = require('q');
var _ = require('lodash');
var path = require('path');
var crc = require('crc');
var exec = Q.denodeify(require('child_process').exec);
var URI = require('urijs');
var pathUtil = require('./path');

var fs = require('./fs');

var GIT_PREFIX = 'git+';
var GIT_TMP = null;


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

// Validates a SHA in hexadecimal
function validateSha(str) {
    return (/[0-9a-f]{40}/).test(str);
}

// Parse and extract infos
function parseGitUrl(giturl) {
    var ref, uri, fileParts, filepath;

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

    uri = new URI(giturl);
    ref = uri.fragment() || 'master';
    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(_.first(fileParts)+'.git');

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

// Clone a git repo from a specific ref
function cloneGitRepo(host, ref) {
    var isBranch = false;

    ref = ref || 'master';
    if (!validateSha(ref)) isBranch = true;

    return Q()

    // Create temporary folder to store git repos
    .then(function() {
        if (GIT_TMP) return;
        return fs.tmp.dir()
            .then(function(_tmp) {
                GIT_TMP = _tmp;
            });
    })

    // Return or clone the git repo
    .then(function() {
        // Unique ID for repo/ref combinaison
        var repoId = crc.crc32(host+'#'+ref).toString(16);

        // Absolute path to the folder
        var repoPath = path.resolve(GIT_TMP, repoId);

        return fs.exists(repoPath)
            .then(function(doExists) {
                if (doExists) return;

                // Clone repo
                return exec('git clone '+host+' '+repoPath)
                .then(function() {
                    return exec('git checkout '+ref, { cwd: repoPath });
                });
            })
            .thenResolve(repoPath);
    });
}

// Get file from a git repo
function resolveFileFromGit(giturl) {
    if (_.isString(giturl)) giturl = parseGitUrl(giturl);
    if (!giturl) return Q(null);

    // Clone or get from cache
    return cloneGitRepo(giturl.host, giturl.ref)
    .then(function(repo) {

        // Resolve relative path
        return path.resolve(repo, giturl.filepath);
    });
}

// Return root of git repo from a filepath
function resolveGitRoot(filepath) {
    var relativeToGit, repoId;

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

    // Extract first directory (is the repo id)
    relativeToGit = path.relative(GIT_TMP, filepath);
    repoId = _.first(relativeToGit.split(path.sep));
    if (!repoId) return;

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


module.exports = {
    checkUrl: checkGitUrl,
    parseUrl: parseGitUrl,
    resolveFile: resolveFileFromGit,
    resolveRoot: resolveGitRoot
};