Skip to content

Commit 1459e9c

Browse files
committed
nodegit: read stashes
1 parent 543126e commit 1459e9c

File tree

2 files changed

+129
-53
lines changed

2 files changed

+129
-53
lines changed

source/git-api.js

Lines changed: 123 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -155,20 +155,38 @@ exports.registerApi = env => {
155155
{ maxWait: 2000 }
156156
);
157157

158-
const autoStashExecuteAndPop = (commands, repoPath, allowedCodes, outPipe, inPipe, timeout) => {
158+
const repoPs = {};
159+
160+
/**
161+
* memoize nodegit opened repos
162+
* @param {string} repoPath the path to the repository
163+
* @returns {Promise<nodegit.Repository>}
164+
*/
165+
const getRepo = repoPath => {
166+
if (!repoPs[repoPath]) {
167+
repoPs[repoPath] = nodegit.Repository.open(repoPath);
168+
}
169+
return repoPs[repoPath];
170+
};
171+
172+
const autoStash = async (repoPath, fn) => {
159173
if (config.autoStashAndPop) {
160-
return gitPromise.stashExecuteAndPop(
161-
commands,
162-
repoPath,
163-
allowedCodes,
164-
outPipe,
165-
inPipe,
166-
timeout
167-
);
174+
const repo = await getRepo(repoPath);
175+
const signature = await repo.defaultSignature();
176+
const oid = await nodegit.Stash.save(repo, signature, 'Ungit: automatic stash', 0);
177+
const out = await fn();
178+
if (!oid) return out;
179+
let index;
180+
await nodegit.Stash.foreach(repo, (i, _msg, stashOid) => {
181+
if (stashOid === oid) index = i;
182+
});
183+
if (index != null) await nodegit.Stash.pop(repo, index);
184+
return out;
168185
} else {
169-
return gitPromise(commands, repoPath, allowedCodes, outPipe, inPipe, timeout);
186+
return fn();
170187
}
171188
};
189+
172190
const jsonResultOrFailProm = (res, promise) =>
173191
// TODO shouldn't this be a boolean instead of an object?
174192
promise
@@ -307,14 +325,19 @@ exports.registerApi = env => {
307325
}
308326
);
309327

310-
app.post(`${exports.pathPrefix}/reset`, ensureAuthenticated, ensurePathExists, (req, res) => {
311-
jsonResultOrFailProm(
312-
res,
313-
autoStashExecuteAndPop(['reset', `--${req.body.mode}`, req.body.to], req.body.path)
314-
)
315-
.then(emitGitDirectoryChanged.bind(null, req.body.path))
316-
.then(emitWorkingTreeChanged.bind(null, req.body.path));
317-
});
328+
app.post(
329+
`${exports.pathPrefix}/reset`,
330+
ensureAuthenticated,
331+
ensurePathExists,
332+
jw(async req => {
333+
const repoPath = req.body.path;
334+
await autoStash(repoPath, () =>
335+
gitPromise(['reset', `--${req.body.mode}`, req.body.to], repoPath)
336+
);
337+
await emitGitDirectoryChanged(repoPath);
338+
await emitWorkingTreeChanged(repoPath);
339+
})
340+
);
318341

319342
app.get(`${exports.pathPrefix}/diff`, ensureAuthenticated, ensurePathExists, (req, res) => {
320343
const isIgnoreWhiteSpace = req.query.whiteSpace === 'true' ? true : false;
@@ -564,12 +587,15 @@ exports.registerApi = env => {
564587
}
565588
);
566589

567-
app.get(`${exports.pathPrefix}/tags`, ensureAuthenticated, ensurePathExists, (req, res) => {
568-
let pathToRepo = req.query.path;
569-
nodegit.Repository.open(pathToRepo).then(function (repo) {
570-
jsonResultOrFailProm(res, nodegit.Tag.list(repo));
571-
});
572-
});
590+
app.get(
591+
`${exports.pathPrefix}/tags`,
592+
ensureAuthenticated,
593+
ensurePathExists,
594+
jw(req => {
595+
let pathToRepo = req.query.path;
596+
return nodegit.Repository.open(pathToRepo).then(repo => nodegit.Tag.list(repo));
597+
})
598+
);
573599

574600
app.get(
575601
`${exports.pathPrefix}/remote/tags`,
@@ -643,28 +669,31 @@ exports.registerApi = env => {
643669
}
644670
);
645671

646-
app.post(`${exports.pathPrefix}/checkout`, ensureAuthenticated, ensurePathExists, (req, res) => {
647-
const arg = !!req.body.sha1
648-
? ['checkout', '-b', req.body.name.trim(), req.body.sha1]
649-
: ['checkout', req.body.name.trim()];
650-
651-
jsonResultOrFailProm(res, autoStashExecuteAndPop(arg, req.body.path))
652-
.then(emitGitDirectoryChanged.bind(null, req.body.path))
653-
.then(emitWorkingTreeChanged.bind(null, req.body.path));
654-
});
672+
app.post(
673+
`${exports.pathPrefix}/checkout`,
674+
ensureAuthenticated,
675+
ensurePathExists,
676+
jw(async req => {
677+
const arg = !!req.body.sha1
678+
? ['checkout', '-b', req.body.name.trim(), req.body.sha1]
679+
: ['checkout', req.body.name.trim()];
680+
const repoPath = req.body.path;
681+
await autoStash(repoPath, () => gitPromise(arg, repoPath));
682+
await emitGitDirectoryChanged(repoPath);
683+
await emitWorkingTreeChanged(repoPath);
684+
})
685+
);
655686

656687
app.post(
657688
`${exports.pathPrefix}/cherrypick`,
658689
ensureAuthenticated,
659690
ensurePathExists,
660-
(req, res) => {
661-
jsonResultOrFailProm(
662-
res,
663-
autoStashExecuteAndPop(['cherry-pick', req.body.name.trim()], req.body.path)
664-
)
665-
.then(emitGitDirectoryChanged.bind(null, req.body.path))
666-
.then(emitWorkingTreeChanged.bind(null, req.body.path));
667-
}
691+
jw(async req => {
692+
const repoPath = req.body.path;
693+
await autoStash(repoPath, () => gitPromise(['cherry-pick', req.body.name.trim()], repoPath));
694+
await emitGitDirectoryChanged(repoPath);
695+
await emitWorkingTreeChanged(repoPath);
696+
})
668697
);
669698

670699
app.get(`${exports.pathPrefix}/checkout`, ensureAuthenticated, ensurePathExists, (req, res) => {
@@ -919,13 +948,60 @@ exports.registerApi = env => {
919948
jsonResultOrFailProm(res, task);
920949
});
921950

922-
app.get(`${exports.pathPrefix}/stashes`, ensureAuthenticated, ensurePathExists, (req, res) => {
923-
const task = gitPromise(
924-
['stash', 'list', '--decorate=full', '--pretty=fuller', '-z', '--parents', '--numstat'],
925-
req.query.path
926-
).then(gitParser.parseGitLog);
927-
jsonResultOrFailProm(res, task);
951+
/**
952+
* @param {nodegit.Commit} c
953+
*/
954+
const formatCommit = c => ({
955+
commitDate: c.date().toJSON(),
956+
message: c.message(),
957+
sha1: c.sha(),
928958
});
959+
/**
960+
* @param {nodegit.Commit} c
961+
*/
962+
const getFileStats = async c => {
963+
const diffList = await c.getDiff();
964+
// Each diff has the entire patch set for some reason
965+
const patches = await diffList[0]?.patches();
966+
if (!patches?.length) return [];
967+
968+
return patches.map(patch => {
969+
const stats = patch.lineStats();
970+
const oldFileName = patch.oldFile().path();
971+
const displayName = patch.newFile().path();
972+
return {
973+
additions: stats.total_additions,
974+
deletions: stats.total_deletions,
975+
fileName: displayName,
976+
oldFileName,
977+
displayName,
978+
// TODO figure out how to get this
979+
type: 'text',
980+
};
981+
});
982+
};
983+
984+
app.get(
985+
`${exports.pathPrefix}/stashes`,
986+
ensureAuthenticated,
987+
ensurePathExists,
988+
jw(async req => {
989+
const repo = await getRepo(req.query.path);
990+
const oids = [];
991+
await nodegit.Stash.foreach(repo, (index, message, oid) => {
992+
oids.push(oid);
993+
});
994+
const stashes = await Promise.all(oids.map(oid => repo.getCommit(oid)));
995+
return Promise.all(
996+
stashes.map(async (stash, index) => ({
997+
...formatCommit(stash),
998+
reflogId: `${index}`,
999+
reflogName: `stash@{${index}}`,
1000+
fileLineDiffs: await getFileStats(stash),
1001+
}))
1002+
);
1003+
})
1004+
);
9291005

9301006
app.post(`${exports.pathPrefix}/stashes`, ensureAuthenticated, ensurePathExists, (req, res) => {
9311007
jsonResultOrFailProm(

source/git-promise.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,13 @@ const gitExecutorProm = (args, retryCount) => {
130130
/**
131131
* Returns a promise that executes git command with given arguments
132132
* @function
133-
* @param {obj|array} commands - An object that represents all parameters or first parameter only, which is an array of commands
134-
* @param {string} repoPath - path to the git repository
133+
* @param {object|Array<string>} commands - An object that represents all parameters or first parameter only, which is an array of commands
134+
* @param {string=} repoPath - path to the git repository
135135
* @param {boolean=} allowError - true if return code of 1 is acceptable as some cases errors are acceptable
136-
* @param {stream=} outPipe - if this argument exists, stdout is piped to this object
137-
* @param {stream=} inPipe - if this argument exists, data is piped to stdin process on start
138-
* @param {timeout=} timeout - execution timeout, default is 2 mins
139-
* @returns {promise} execution promise
136+
* @param {Stream=} outPipe - if this argument exists, stdout is piped to this object
137+
* @param {Stream=} inPipe - if this argument exists, data is piped to stdin process on start
138+
* @param {number=} timeout - execution timeout in ms, default is 2 mins
139+
* @returns {Promise<string>} execution promise
140140
* @example getGitExecuteTask({ commands: ['show'], repoPath: '/tmp' });
141141
* @example getGitExecuteTask(['show'], '/tmp');
142142
*/

0 commit comments

Comments
 (0)