238 lines
7.5 KiB
JavaScript
238 lines
7.5 KiB
JavaScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
const exec = require('child_process').exec;
|
|
const fs = require('fs');
|
|
const DevConfig = require('./dev-config');
|
|
const Utils = require('./utils');
|
|
|
|
/**
|
|
* Class to run git commands.
|
|
*/
|
|
class Git {
|
|
|
|
/**
|
|
* Create a patch.
|
|
*
|
|
* @param range Show only commits in the specified revision range.
|
|
* @param saveTo Path to the file to save the patch to. If not defined, the patch contents will be returned.
|
|
* @return Promise resolved when done. If saveTo not provided, it will return the patch contents.
|
|
*/
|
|
createPatch(range, saveTo) {
|
|
return new Promise((resolve, reject) => {
|
|
exec(`git format-patch ${range} --stdout`, (err, result) => {
|
|
if (err) {
|
|
reject(err);
|
|
return;
|
|
}
|
|
|
|
if (!saveTo) {
|
|
resolve(result);
|
|
return;
|
|
}
|
|
|
|
// Save it to a file.
|
|
const directory = saveTo.substring(0, saveTo.lastIndexOf('/'));
|
|
if (directory && directory != '.' && directory != '..' && !fs.existsSync(directory)) {
|
|
fs.mkdirSync(directory);
|
|
}
|
|
fs.writeFileSync(saveTo, result);
|
|
|
|
resolve();
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get current branch.
|
|
*
|
|
* @return Promise resolved with the branch name.
|
|
*/
|
|
getCurrentBranch() {
|
|
return new Promise((resolve, reject) => {
|
|
exec('git branch --show-current', (err, branch) => {
|
|
if (branch) {
|
|
resolve(branch.replace('\n', ''));
|
|
} else {
|
|
reject (err || 'Current branch not found.');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the HEAD commit for a certain branch.
|
|
*
|
|
* @param branch Name of the branch.
|
|
* @param branchData Parsed branch data. If not provided it will be calculated.
|
|
* @return HEAD commit.
|
|
*/
|
|
async getHeadCommit(branch, branchData) {
|
|
if (!branchData) {
|
|
// Parse the branch to get the project and issue number.
|
|
branchData = Utils.parseBranch(branch);
|
|
}
|
|
|
|
// Loop over the last commits to find the first commit messages that doesn't belong to the issue.
|
|
const commitsString = await this.log(50, branch, '%s_____%H');
|
|
const commits = commitsString.split('\n');
|
|
commits.pop(); // Remove last element, it's an empty string.
|
|
|
|
for (let i = 0; i < commits.length; i++) {
|
|
const commit = commits[i];
|
|
const match = Utils.getIssueFromCommitMessage(commit) == branchData.issue;
|
|
|
|
if (i === 0 && !match) {
|
|
// Most recent commit doesn't belong to the issue. Stop looking.
|
|
break;
|
|
}
|
|
|
|
if (!match) {
|
|
// The commit does not match any more, we found it!
|
|
return commit.split('_____')[1];
|
|
}
|
|
}
|
|
|
|
// Couldn't find the commit using the commit names, get the last commit in the integration branch.
|
|
const remote = DevConfig.get('upstreamRemote', 'origin');
|
|
console.log(`Head commit not found using commit messages. Get last commit from ${remote}/integration`);
|
|
const hashes = await this.hashes(1, `${remote}/integration`);
|
|
|
|
return hashes[0];
|
|
}
|
|
|
|
/**
|
|
* Get the URL of a certain remote.
|
|
*
|
|
* @param remote Remote name.
|
|
* @return Promise resolved with the remote URL.
|
|
*/
|
|
getRemoteUrl(remote) {
|
|
return new Promise((resolve, reject) => {
|
|
exec(`git remote get-url ${remote}`, (err, url) => {
|
|
if (url) {
|
|
resolve(url.replace('\n', ''));
|
|
} else {
|
|
reject (err || 'Remote not found.');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Return the latest hashes from git log.
|
|
*
|
|
* @param count Number of commits to display.
|
|
* @param range Show only commits in the specified revision range.
|
|
* @param format Pretty-print the contents of the commit logs in a given format.
|
|
* @return Promise resolved with the list of hashes.
|
|
*/
|
|
async hashes(count, range, format) {
|
|
format = format || '%H';
|
|
|
|
const hashList = await this.log(count, range, format);
|
|
|
|
const hashes = hashList.split('\n');
|
|
hashes.pop(); // Remove last element, it's an empty string.
|
|
|
|
return hashes;
|
|
}
|
|
|
|
/**
|
|
* Calls the log command and returns the raw output.
|
|
*
|
|
* @param count Number of commits to display.
|
|
* @param range Show only commits in the specified revision range.
|
|
* @param format Pretty-print the contents of the commit logs in a given format.
|
|
* @param path Show only commits that are enough to explain how the files that match the specified paths came to be.
|
|
* @return Promise resolved with the result.
|
|
*/
|
|
log(count, range, format, path) {
|
|
if (typeof count == 'undefined') {
|
|
count = 10;
|
|
}
|
|
|
|
let command = 'git log';
|
|
|
|
if (count > 0) {
|
|
command += ` -n ${count} `;
|
|
}
|
|
if (format) {
|
|
command += ` --format=${format} `;
|
|
}
|
|
if (range){
|
|
command += ` ${range} `;
|
|
}
|
|
if (path) {
|
|
command += ` -- ${path}`;
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
exec(command, (err, result, stderr) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(result);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Return the latest titles of the commit messages.
|
|
*
|
|
* @param count Number of commits to display.
|
|
* @param range Show only commits in the specified revision range.
|
|
* @param path Show only commits that are enough to explain how the files that match the specified paths came to be.
|
|
* @return Promise resolved with the list of titles.
|
|
*/
|
|
async messages(count, range, path) {
|
|
count = typeof count != 'undefined' ? count : 10;
|
|
|
|
const messageList = await this.log(count, range, '%s', path);
|
|
|
|
const messages = messageList.split('\n');
|
|
messages.pop(); // Remove last element, it's an empty string.
|
|
|
|
return messages;
|
|
}
|
|
|
|
/**
|
|
* Push a branch.
|
|
*
|
|
* @param remote Remote to use.
|
|
* @param branch Branch to push.
|
|
* @param force Whether to force the push.
|
|
* @return Promise resolved when done.
|
|
*/
|
|
push(remote, branch, force) {
|
|
return new Promise((resolve, reject) => {
|
|
let command = `git push ${remote} ${branch}`;
|
|
if (force) {
|
|
command += ' -f';
|
|
}
|
|
|
|
exec(command, (err, result, stderr) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = new Git();
|