Skip to content

Commit

Permalink
nodegit: read stashes
Browse files Browse the repository at this point in the history
  • Loading branch information
wmertens committed Apr 27, 2020
1 parent 9fe1bec commit 309964f
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 53 deletions.
170 changes: 123 additions & 47 deletions source/git-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,20 +167,38 @@ exports.registerApi = (env) => {
{ maxWait: 2000 }
);

const autoStashExecuteAndPop = (commands, repoPath, allowedCodes, outPipe, inPipe, timeout) => {
const repoPs = {};

/**
* memoize nodegit opened repos
* @param {string} repoPath the path to the repository
* @returns {Promise<nodegit.Repository>}
*/
const getRepo = repoPath => {
if (!repoPs[repoPath]) {
repoPs[repoPath] = nodegit.Repository.open(repoPath);
}
return repoPs[repoPath];
};

const autoStash = async (repoPath, fn) => {
if (config.autoStashAndPop) {
return gitPromise.stashExecuteAndPop(
commands,
repoPath,
allowedCodes,
outPipe,
inPipe,
timeout
);
const repo = await getRepo(repoPath);
const signature = await repo.defaultSignature();
const oid = await nodegit.Stash.save(repo, signature, 'Ungit: automatic stash', 0);
const out = await fn();
if (!oid) return out;
let index;
await nodegit.Stash.foreach(repo, (i, _msg, stashOid) => {
if (stashOid === oid) index = i;
});
if (index != null) await nodegit.Stash.pop(repo, index);
return out;
} else {
return gitPromise(commands, repoPath, allowedCodes, outPipe, inPipe, timeout);
return fn();
}
};

const jsonResultOrFailProm = (res, promise) =>
// TODO shouldn't this be a boolean instead of an object?
promise
Expand Down Expand Up @@ -319,14 +337,19 @@ exports.registerApi = (env) => {
}
);

app.post(`${exports.pathPrefix}/reset`, ensureAuthenticated, ensurePathExists, (req, res) => {
jsonResultOrFailProm(
res,
autoStashExecuteAndPop(['reset', `--${req.body.mode}`, req.body.to], req.body.path)
)
.then(emitGitDirectoryChanged.bind(null, req.body.path))
.then(emitWorkingTreeChanged.bind(null, req.body.path));
});
app.post(
`${exports.pathPrefix}/reset`,
ensureAuthenticated,
ensurePathExists,
jw(async req => {
const repoPath = req.body.path;
await autoStash(repoPath, () =>
gitPromise(['reset', `--${req.body.mode}`, req.body.to], repoPath)
);
await emitGitDirectoryChanged(repoPath);
await emitWorkingTreeChanged(repoPath);
})
);

app.get(`${exports.pathPrefix}/diff`, ensureAuthenticated, ensurePathExists, (req, res) => {
const isIgnoreWhiteSpace = req.query.whiteSpace === 'true' ? true : false;
Expand Down Expand Up @@ -576,12 +599,15 @@ exports.registerApi = (env) => {
}
);

app.get(`${exports.pathPrefix}/tags`, ensureAuthenticated, ensurePathExists, (req, res) => {
let pathToRepo = req.query.path;
nodegit.Repository.open(pathToRepo).then(function (repo) {
jsonResultOrFailProm(res, nodegit.Tag.list(repo));
});
});
app.get(
`${exports.pathPrefix}/tags`,
ensureAuthenticated,
ensurePathExists,
jw(req => {
let pathToRepo = req.query.path;
return nodegit.Repository.open(pathToRepo).then(repo => nodegit.Tag.list(repo));
})
);

app.get(
`${exports.pathPrefix}/remote/tags`,
Expand Down Expand Up @@ -655,28 +681,31 @@ exports.registerApi = (env) => {
}
);

app.post(`${exports.pathPrefix}/checkout`, ensureAuthenticated, ensurePathExists, (req, res) => {
const arg = !!req.body.sha1
? ['checkout', '-b', req.body.name.trim(), req.body.sha1]
: ['checkout', req.body.name.trim()];

jsonResultOrFailProm(res, autoStashExecuteAndPop(arg, req.body.path))
.then(emitGitDirectoryChanged.bind(null, req.body.path))
.then(emitWorkingTreeChanged.bind(null, req.body.path));
});
app.post(
`${exports.pathPrefix}/checkout`,
ensureAuthenticated,
ensurePathExists,
jw(async req => {
const arg = !!req.body.sha1
? ['checkout', '-b', req.body.name.trim(), req.body.sha1]
: ['checkout', req.body.name.trim()];
const repoPath = req.body.path;
await autoStash(repoPath, () => gitPromise(arg, repoPath));
await emitGitDirectoryChanged(repoPath);
await emitWorkingTreeChanged(repoPath);
})
);

app.post(
`${exports.pathPrefix}/cherrypick`,
ensureAuthenticated,
ensurePathExists,
(req, res) => {
jsonResultOrFailProm(
res,
autoStashExecuteAndPop(['cherry-pick', req.body.name.trim()], req.body.path)
)
.then(emitGitDirectoryChanged.bind(null, req.body.path))
.then(emitWorkingTreeChanged.bind(null, req.body.path));
}
jw(async req => {
const repoPath = req.body.path;
await autoStash(repoPath, () => gitPromise(['cherry-pick', req.body.name.trim()], repoPath));
await emitGitDirectoryChanged(repoPath);
await emitWorkingTreeChanged(repoPath);
})
);

app.get(`${exports.pathPrefix}/checkout`, ensureAuthenticated, ensurePathExists, (req, res) => {
Expand Down Expand Up @@ -931,13 +960,60 @@ exports.registerApi = (env) => {
jsonResultOrFailProm(res, task);
});

app.get(`${exports.pathPrefix}/stashes`, ensureAuthenticated, ensurePathExists, (req, res) => {
const task = gitPromise(
['stash', 'list', '--decorate=full', '--pretty=fuller', '-z', '--parents', '--numstat'],
req.query.path
).then(gitParser.parseGitLog);
jsonResultOrFailProm(res, task);
/**
* @param {nodegit.Commit} c
*/
const formatCommit = c => ({
commitDate: c.date().toJSON(),
message: c.message(),
sha1: c.sha(),
});
/**
* @param {nodegit.Commit} c
*/
const getFileStats = async c => {
const diffList = await c.getDiff();
// Each diff has the entire patch set for some reason
const patches = await diffList[0]?.patches();
if (!patches?.length) return [];

return patches.map(patch => {
const stats = patch.lineStats();
const oldFileName = patch.oldFile().path();
const displayName = patch.newFile().path();
return {
additions: stats.total_additions,
deletions: stats.total_deletions,
fileName: displayName,
oldFileName,
displayName,
// TODO figure out how to get this
type: 'text',
};
});
};

app.get(
`${exports.pathPrefix}/stashes`,
ensureAuthenticated,
ensurePathExists,
jw(async req => {
const repo = await getRepo(req.query.path);
const oids = [];
await nodegit.Stash.foreach(repo, (index, message, oid) => {
oids.push(oid);
});
const stashes = await Promise.all(oids.map(oid => repo.getCommit(oid)));
return Promise.all(
stashes.map(async (stash, index) => ({
...formatCommit(stash),
reflogId: `${index}`,
reflogName: `stash@{${index}}`,
fileLineDiffs: await getFileStats(stash),
}))
);
})
);

app.post(`${exports.pathPrefix}/stashes`, ensureAuthenticated, ensurePathExists, (req, res) => {
jsonResultOrFailProm(
Expand Down
12 changes: 6 additions & 6 deletions source/git-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,13 @@ const gitExecutorProm = (args, retryCount) => {
/**
* Returns a promise that executes git command with given arguments
* @function
* @param {obj|array} commands - An object that represents all parameters or first parameter only, which is an array of commands
* @param {string} repoPath - path to the git repository
* @param {object|Array<string>} commands - An object that represents all parameters or first parameter only, which is an array of commands
* @param {string=} repoPath - path to the git repository
* @param {boolean=} allowError - true if return code of 1 is acceptable as some cases errors are acceptable
* @param {stream=} outPipe - if this argument exists, stdout is piped to this object
* @param {stream=} inPipe - if this argument exists, data is piped to stdin process on start
* @param {timeout=} timeout - execution timeout, default is 2 mins
* @returns {promise} execution promise
* @param {Stream=} outPipe - if this argument exists, stdout is piped to this object
* @param {Stream=} inPipe - if this argument exists, data is piped to stdin process on start
* @param {number=} timeout - execution timeout in ms, default is 2 mins
* @returns {Promise<string>} execution promise
* @example getGitExecuteTask({ commands: ['show'], repoPath: '/tmp' });
* @example getGitExecuteTask(['show'], '/tmp');
*/
Expand Down

0 comments on commit 309964f

Please sign in to comment.