1 | module.exports = gitlog
|
2 | var exec = require('child_process').exec
|
3 | , debug = require('debug')('gitlog')
|
4 | , extend = require('lodash.assign')
|
5 | , delimiter = '\t'
|
6 | , fields =
|
7 | { hash: '%H'
|
8 | , abbrevHash: '%h'
|
9 | , treeHash: '%T'
|
10 | , abbrevTreeHash: '%t'
|
11 | , parentHashes: '%P'
|
12 | , abbrevParentHashes: '%P'
|
13 | , authorName: '%an'
|
14 | , authorEmail: '%ae'
|
15 | , authorDate: '%ai'
|
16 | , authorDateRel: '%ar'
|
17 | , committerName: '%cn'
|
18 | , committerEmail: '%ce'
|
19 | , committerDate: '%cd'
|
20 | , committerDateRel: '%cr'
|
21 | , subject: '%s'
|
22 | , body: '%B'
|
23 | }
|
24 | , notOptFields = [ 'status', 'files' ]
|
25 |
|
26 |
|
27 |
|
28 |
|
29 | function addOptional(command, options) {
|
30 | var cmdOptional = [ 'author', 'since', 'after', 'until', 'before', 'committer' ]
|
31 | for (var i = cmdOptional.length; i--;) {
|
32 | if (options[cmdOptional[i]]) {
|
33 | command += ' --' + cmdOptional[i] + '="' + options[cmdOptional[i]] + '"'
|
34 | }
|
35 | }
|
36 | return command
|
37 | }
|
38 |
|
39 | function gitlog(options, cb) {
|
40 | if (!options.repo) throw new Error('Repo required!')
|
41 | if (!cb) throw new Error('Callback required!')
|
42 |
|
43 | var defaultOptions =
|
44 | { number: 10
|
45 | , fields: [ 'abbrevHash', 'hash', 'subject', 'authorName' ]
|
46 | , nameStatus:true
|
47 | , findCopiesHarder:false
|
48 | , execOptions: {}
|
49 | }
|
50 |
|
51 |
|
52 | options = extend(defaultOptions, options)
|
53 |
|
54 | var prevWorkingDir = process.cwd()
|
55 | try {
|
56 | process.chdir(options.repo)
|
57 | } catch (e) {
|
58 | throw new Error('Repo location does not exist')
|
59 | }
|
60 |
|
61 |
|
62 | var command = 'git log '
|
63 |
|
64 | if (options.findCopiesHarder){
|
65 | command += '--find-copies-harder '
|
66 | }
|
67 |
|
68 | command += '-n ' + options.number
|
69 |
|
70 | command = addOptional(command, options)
|
71 |
|
72 |
|
73 | command += ' --pretty="@begin@'
|
74 |
|
75 |
|
76 | options.fields.forEach(function(field) {
|
77 | if (!fields[field] && field.indexOf(notOptFields) === -1) throw new Error('Unknown field: ' + field)
|
78 | command += delimiter + fields[field]
|
79 | })
|
80 |
|
81 |
|
82 | command += '@end@"'
|
83 |
|
84 |
|
85 | if (options.branch) {
|
86 | command += ' ' + options.branch
|
87 | }
|
88 |
|
89 | if (options.file) {
|
90 | command += ' -- ' + options.file
|
91 | }
|
92 |
|
93 |
|
94 | command += fileNameAndStatus(options)
|
95 |
|
96 | debug('command', options.execOptions, command)
|
97 | exec(command, options.execOptions, function(err, stdout, stderr) {
|
98 | debug('stdout',stdout)
|
99 | var commits = stdout.split('\n@begin@')
|
100 | if (commits.length === 1 && commits[0] === '' ){
|
101 | commits.shift()
|
102 | }
|
103 | debug('commits',commits)
|
104 |
|
105 | commits = parseCommits(commits, options.fields, options.nameStatus)
|
106 |
|
107 | cb(stderr || err, commits)
|
108 | })
|
109 |
|
110 | process.chdir(prevWorkingDir);
|
111 | }
|
112 |
|
113 | function fileNameAndStatus(options) {
|
114 | return options.nameStatus ? ' --name-status' : '';
|
115 | }
|
116 |
|
117 | function parseCommits(commits, fields, nameStatus) {
|
118 | return commits.map(function(commit) {
|
119 | var parts = commit.split('@end@\n\n')
|
120 |
|
121 | commit = parts[0].split(delimiter)
|
122 |
|
123 | if (parts[1]) {
|
124 | var parseNameStatus = parts[1].split('\n');
|
125 |
|
126 |
|
127 | if (parseNameStatus[parseNameStatus.length - 1] === ''){
|
128 | parseNameStatus.pop()
|
129 | }
|
130 |
|
131 |
|
132 | parseNameStatus.forEach(function(d, i) {
|
133 | parseNameStatus[i] = d.split(delimiter);
|
134 | });
|
135 |
|
136 |
|
137 |
|
138 | parseNameStatus = parseNameStatus.reduce(function(a, b) {
|
139 | var tempArr = [ b[ 0 ], b[ b.length - 1 ] ];
|
140 |
|
141 |
|
142 | for (var i = 1, len = b.length - 1; i < len; i++) {
|
143 |
|
144 |
|
145 | if (b[ 0 ].slice(0, 1) === 'R'){
|
146 | tempArr.push('D', b[ i ]);
|
147 | }
|
148 | }
|
149 |
|
150 | return a.concat(tempArr);
|
151 | }, [])
|
152 |
|
153 | commit = commit.concat(parseNameStatus)
|
154 | }
|
155 |
|
156 | debug('commit', commit)
|
157 |
|
158 |
|
159 | commit.shift()
|
160 |
|
161 | var parsed = {}
|
162 |
|
163 | if (nameStatus){
|
164 |
|
165 | notOptFields.forEach(function(d) {
|
166 | parsed[d] = [];
|
167 | })
|
168 | }
|
169 |
|
170 | commit.forEach(function(commitField, index) {
|
171 | if (fields[index]) {
|
172 | parsed[fields[index]] = commitField
|
173 | } else {
|
174 | if (nameStatus){
|
175 | var pos = (index - fields.length) % notOptFields.length
|
176 |
|
177 | debug('nameStatus', (index - fields.length) ,notOptFields.length,pos,commitField)
|
178 | parsed[notOptFields[pos]].push(commitField)
|
179 | }
|
180 | }
|
181 | })
|
182 |
|
183 | return parsed
|
184 | })
|
185 | }
|