Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use nodegit #1315

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
5a5d89e
deps: fix fs-events outside macOS on Node 14
wmertens May 23, 2022
d2336c7
git-api: add promise wrappers for handlers
wmertens Apr 23, 2020
c7751e8
git-api: document types and increase sanity
wmertens Mar 30, 2022
2df4bce
lint: re-enable tsc js checking + fix errors
wmertens May 23, 2022
253ed24
git-api: elapsed time on X-elapsed header
wmertens Feb 10, 2021
537012e
git-api: /head: return object
wmertens Mar 30, 2022
7033598
color edges according to node
wmertens Feb 16, 2021
eee5ddb
Turn off loadahead throbber
wmertens Feb 12, 2021
ae5d5d5
repository: don't hide graph during rebase
wmertens Feb 18, 2021
1fc0b53
direnv: configure, with nix flakes
wmertens May 23, 2022
2db2d87
flake fixup
wmertens Sep 13, 2022
6c3a361
git-api: use nodegit
wmertens Mar 30, 2022
8f774b6
nodegit: own file, autostash, lots of error wraps
wmertens Jul 16, 2022
49c7c55
git-api: refactor: put repo on req
wmertens Feb 9, 2021
94e8b47
nodegit: move into NGWrap class
wmertens Feb 9, 2021
2afd377
nodegit: stash: don't error on empty
wmertens Feb 14, 2021
f3dc504
nodegit: add status
wmertens Feb 9, 2021
0c31897
nodegit: gitlog
wmertens Feb 10, 2021
88c1967
nodegit: refs
wmertens Jul 16, 2022
c611083
api: nodegitify branches and fetching
wmertens Jul 18, 2022
39ee6e0
nodegit diffFile
wmertens Feb 14, 2021
dd6e171
getDiff: diff against any oid
wmertens Jul 16, 2022
64f1cbd
nodegit: invalidate cached repo on change
wmertens Feb 28, 2021
caf4144
getDiff: index,worktree
wmertens Feb 28, 2021
e2b4be7
commitDiff: calc displayName
wmertens Feb 28, 2021
1e534f5
getStashes: also show new files
wmertens Feb 28, 2021
4c0a0bc
staging: use combined diff
wmertens Mar 30, 2022
fe7d720
staging: allow editing message during rebase
wmertens May 5, 2022
1526e0e
remotes: don't block initial load
wmertens May 5, 2022
1679564
graph/git-ref: fix not displaying on init
wmertens May 9, 2022
8ce61cc
graph: improve layout and get commits on demand
wmertens Jul 16, 2022
482e863
todo
wmertens May 10, 2022
4fe9a49
nodegit: fix diff for new files
wmertens May 17, 2022
ac03f6d
remotes: actually fetch when clicking fetch
wmertens Jun 3, 2022
decff6a
staging mode WIP
wmertens Jul 16, 2022
1f797a3
direnv fixup
wmertens Sep 7, 2022
b9a9b42
fixup commit
wmertens Sep 13, 2022
30a9598
flake update
wmertens Oct 11, 2022
ea76d28
bump
wmertens Aug 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .envrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
watch_file package-lock.json
use flake
layout node
10 changes: 9 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@
"root": true,
"extends": [
"eslint:recommended",
"plugin:mocha/recommended",
"plugin:prettier/recommended"
],
"globals": {
"io": "readonly",
"jQuery": "writeable",
"Raven": "readonly",
"ungit": "readonly"
},
"parserOptions": {
"ecmaVersion": 2020
},
"env": {
"browser": true,
"es6": true,
"node": true
},
Expand All @@ -28,4 +36,4 @@
}
]
}
}
}
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Ignore generated folders
.direnv
.nyc_output/
build/
coverage/
Expand All @@ -18,3 +19,4 @@ components/**/*.css.map
.vscode/
.ungitrc
ungit-*.tgz

25 changes: 25 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# TODO

## bugs

- conflicts don't always have a diff
- diff sometimes includes multiple deletions of the same file
- new file shows as modified, with 0 additions and an oldFileName but not newFileName
- new adds [new]
- remote-tags-updated does not fetch new remote refs and update the tree
- move watcher to repo class

## ideas

- staging: show staged files inline with checkboxes
- [x] useStaging mode when index is populated
- [ ] when clicking checkbox in useStaging, stage/unstage
- [ ] when selecting text in diff let patch button add selected range to staged and switch to useStaging
- [ ] Allow sorting branches by local first or just by date
- [ ] alias all commits with numeric ids to limit mem use on client
- [ ] load commit existence and parents separately from metadata
- [ ] possibly cache them in a db so the entire tree, if loading commit tree from git is slow
- [ ] conflict diffs should be shown better, like in vscode
- [ ] be able to expand diff context, like on github

- [ ] CRDT sync between TODO file and github issue :drool:
122 changes: 62 additions & 60 deletions components/branches/branches.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ components.register('branches', (args) => {
});

class BranchesViewModel extends ComponentRoot {
constructor(server, graph, repoPath) {
constructor(server, /** @type {GitGraph} */ graph, repoPath) {
super();
this.repoPath = repoPath;
this.server = server;
this.updateRefs = _.debounce(this._updateRefs, 250, this.defaultDebounceOption);
this.branchesAndLocalTags = ko.observableArray();
this.current = ko.observable();
this.isShowRemote = ko.observable(storage.getItem(showRemote) != 'false');
Expand Down Expand Up @@ -45,6 +44,7 @@ class BranchesViewModel extends ComponentRoot {
this.refsLabel = ko.computed(() => this.current() || 'master (no commits yet)');
this.branchIcon = octicons['git-branch'].toSVG({ height: 18 });
this.closeIcon = octicons.x.toSVG({ height: 18 });
this.firstFetch = true;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of doing firstFetch flag. Maybe we should do something like this?

this.updateRefs = () => {}
this._updateRefs(false)
  .then(() => this._updateRefs(true))
  .then(() => { this.updateRefs = _.throttle(this._updateRefs, 500); });

Although, I'm little confused on why this quick succession of same calls are required?

}

checkoutBranch(branch) {
Expand All @@ -66,75 +66,77 @@ class BranchesViewModel extends ComponentRoot {
this.updateRefs();
}
}
async _updateRefs(forceRemoteFetch) {
updateRefs(forceRemoteFetch) {
forceRemoteFetch = forceRemoteFetch || this.shouldAutoFetch || '';
if (this.firstFetch) forceRemoteFetch = '';
Comment on lines 70 to +71
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should separate these logics to isForceRemoteFetch() function? Logic is getting convoluted here. Also this is probably my mistake but we should also do false instead of ''.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yes - having the first fetch be remote makes it slow, that's why it does it twice.

Perhaps some of this logic should move to the server side, where it can more easily decide when to git-fetch and can send events if the refs change.


const branchesProm = this.server.getPromise('/branches', { path: this.repoPath() });
const refsProm = this.server.getPromise('/refs', {
path: this.repoPath(),
remoteFetch: forceRemoteFetch,
});
const currentBranchProm = this.server
.getPromise('/checkout', { path: this.repoPath() })
.then((branch) => this.current(branch))
.catch((err) => this.current('~error'));

try {
// set current branch
(await branchesProm).forEach((b) => {
if (b.current) {
this.current(b.name);
// refreshes tags branches and remote branches
// TODO refresh remote refs separately, notify autofetch via ws
const refsProm = this.server
.getPromise('/refs', { path: this.repoPath(), remoteFetch: forceRemoteFetch })
.then((refs) => {
const stamp = Date.now();
const locals = [];
for (const { name, sha1, date } of refs) {
const lname = name.replace('refs/tags', 'tag: refs/tags');
// side effect: registers the ref
const ref = this.graph.getRef(lname, sha1);
const node = ref.node();
if (date && !node.isInited()) {
const ts = Date.parse(date);
// Push down uninited nodes based on date
if (!node.date || node.date > ts) node.date = ts;
}
ref.stamp = stamp;
const { localRefName, isRemote, isBranch, isTag } = ref;
if (
!(
localRefName == 'refs/stash' ||
// Remote HEAD
localRefName.endsWith('/HEAD') ||
(isRemote && !this.isShowRemote()) ||
(isBranch && !this.isShowBranch()) ||
(isTag && !this.isShowTag())
)
)
locals.push(ref);
}
});
} catch (e) {
this.current('~error');
ungit.logger.warn('error while setting current branch', e);
}

try {
// update branches and tags references.
const refs = await refsProm;
if (this.isSamePayload(refs)) {
return;
}

const version = Date.now();
const sorted = refs
.map((r) => {
const ref = this.graph.getRef(r.name.replace('refs/tags', 'tag: refs/tags'));
ref.node(this.graph.getNode(r.sha1));
ref.version = version;
return ref;
})
.sort((a, b) => {
locals.sort((a, b) => {
if (a.current() || b.current()) {
// Current branch is always first
return a.current() ? -1 : 1;
} else if (a.isRemoteBranch === b.isRemoteBranch) {
if (a.name < b.name) {
return -1;
}
if (a.name > b.name) {
return 1;
}
return 0;
} else {
} else if (a.isRemoteBranch !== b.isRemoteBranch) {
// Remote branches show last
return a.isRemoteBranch ? 1 : -1;
} else {
// Otherwise, sort by name, grouped by remoteness
return a.name < b.name ? -1 : a.name > b.name ? 1 : 0;
}
})
.filter((ref) => {
if (ref.localRefName == 'refs/stash') return false;
if (ref.localRefName.endsWith('/HEAD')) return false;
if (!this.isShowRemote() && ref.isRemote) return false;
if (!this.isShowBranch() && ref.isBranch) return false;
if (!this.isShowTag() && ref.isTag) return false;
return true;
});
this.branchesAndLocalTags(sorted);
this.graph.refs().forEach((ref) => {
// ref was removed from another source
if (!ref.isRemoteTag && ref.value !== 'HEAD' && (!ref.version || ref.version < version)) {
ref.remove(true);
}
this.branchesAndLocalTags(locals);
this.graph.refs().forEach((ref) => {
// ref was removed from another source
if (!ref.isRemoteTag && ref.value !== 'HEAD' && ref.stamp !== stamp) {
ref.remove(true);
}
});
this.graph.fetchCommits();
})
.catch((e) => this.server.unhandledRejection(e));

if (this.firstFetch) {
refsProm.then(() => {
this.firstFetch = false;
// Fetch remotes on first load
this.updateRefs(true);
});
} catch (e) {
ungit.logger.error('error during branch update: ', e);
}
return Promise.all([currentBranchProm, refsProm]);
}

branchRemove(branch) {
Expand Down
2 changes: 2 additions & 0 deletions components/commit/commit.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
class="commit"
data-bind="css: { highlighted: highlighted, hover: nodeIsMousehover, selected: selected }"
>
<!-- ko if: isInited() && title() -->
<div
class="commit-box panel panel-default"
data-bind="element: element, click: stopClickPropagation"
Expand Down Expand Up @@ -66,4 +67,5 @@
<!-- /ko -->
</div>
</div>
<!-- /ko -->
</div>
16 changes: 10 additions & 6 deletions components/commit/commit.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ const components = require('ungit-components');
components.register('commit', (args) => new CommitViewModel(args));

class CommitViewModel {
constructor(gitNode) {
constructor(/** @type {GraphNode} */ gitNode) {
this.repoPath = gitNode.graph.repoPath;
this.sha1 = gitNode.sha1;
this.isInited = ko.observable(false);
this.server = gitNode.graph.server;
this.highlighted = gitNode.highlighted;
this.nodeIsMousehover = gitNode.nodeIsMousehover;
Expand All @@ -18,9 +19,10 @@ class CommitViewModel {
this.pgpIcon = octicons.verified.toSVG({ height: 18 });
this.element = ko.observable();
this.message = ko.observable();
this.commitDiff = ko.observable();
this.title = ko.observable();
this.body = ko.observable();
this.authorDate = ko.observable();
this.authorDate = ko.observable(/** @type {moment.Moment} */ (null));
this.authorDateFromNow = ko.observable();
this.authorName = ko.observable();
this.authorEmail = ko.observable();
Expand All @@ -34,7 +36,7 @@ class CommitViewModel {
);

this.diffStyle = ko.computed(() => {
const marginLeft = Math.min(gitNode.branchOrder() * 70, 450) * -1;
const marginLeft = Math.min(gitNode.slot() * 70, 450) * -1;
if (this.selected() && this.element())
return { 'margin-left': `${marginLeft}px`, width: `${window.innerWidth - 220}px` };
else return {};
Expand All @@ -45,7 +47,7 @@ class CommitViewModel {
ko.renderTemplate('commit', this, {}, parentElement);
}

setData(args) {
setData(/** @type {Commit} */ args) {
const message = args.message.split('\n');
this.message(args.message);
this.title(message[0]);
Expand All @@ -57,10 +59,11 @@ class CommitViewModel {
this.numberOfAddedLines(args.additions);
this.numberOfRemovedLines(args.deletions);
this.fileLineDiffs(args.fileLineDiffs);
this.commitDiff = ko.observable(
this.isInited(true);
this.commitDiff(
components.create('commitDiff', {
fileLineDiffs: this.fileLineDiffs(),
sha1: this.sha1,
diffKey: args.diffKey,
repoPath: this.repoPath,
server: this.server,
showDiffButtons: this.selected,
Expand All @@ -69,6 +72,7 @@ class CommitViewModel {
}

updateLastAuthorDateFromNow(deltaT) {
if (!this.isInited()) return;
this.lastUpdatedAuthorDateFromNow = this.lastUpdatedAuthorDateFromNow || 0;
this.lastUpdatedAuthorDateFromNow += deltaT;
if (this.lastUpdatedAuthorDateFromNow > 60 * 1000) {
Expand Down
2 changes: 1 addition & 1 deletion components/commitdiff/commitdiff.html
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<div class="file">
<div class="head commit-diff-filename" data-bind="click: fileNameClick">
<span data-bind="text: displayName"></span>
<span class="file-stats" data-bind="visible: added() != '-'">
<span class="file-stats" data-bind="visible: added() != null">
(
<span data-bind="text: added"></span>
<span data-bind="text: removed"></span>
Expand Down
2 changes: 1 addition & 1 deletion components/commitdiff/commitdiff.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ components.register('commitDiff', (args) => new CommitDiff(args));

class CommitDiff {
constructor(args) {
this.sha1 = args.sha1;
this.diffKey = args.diffKey;
wmertens marked this conversation as resolved.
Show resolved Hide resolved

this.showDiffButtons = args.showDiffButtons;
this.textDiffType = args.textDiffType = args.textDiffType || components.create('textdiff.type');
Expand Down
45 changes: 36 additions & 9 deletions components/commitdiff/commitlinediff.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,38 @@ const components = require('ungit-components');
const programEvents = require('ungit-program-events');

class CommitLineDiff {
constructor(args, fileLineDiff) {
this.added = ko.observable(fileLineDiff.additions);
this.removed = ko.observable(fileLineDiff.deletions);
this.fileName = ko.observable(fileLineDiff.fileName);
this.oldFileName = ko.observable(fileLineDiff.oldFileName);
this.displayName = ko.observable(fileLineDiff.displayName);
this.fileType = fileLineDiff.type;
constructor(args, /** @type {DiffStat} */ data) {
this.added = ko.observable(data.additions);
this.removed = ko.observable(data.deletions);

// TODO remove
this.fileName = ko.observable(data.fileName);
this.oldFileName = ko.observable(data.oldFileName);
this.sha1 = args.sha1;

this.diffKey = args.diffKey;
const { fileName, oldFileName, idx } = data;
this.idx = idx;

this.isNew = !oldFileName;
this.wasRemoved = !fileName;
this.hasConflict = data.hasConflict;
this.renamed = data.oldFileName && data.fileName && data.oldFileName !== data.fileName;

this.displayName = ko.observable(
fileName
? oldFileName
? oldFileName !== fileName
? `${oldFileName} → ${fileName}`
: fileName
: `[new] ${fileName}`
: `[del] ${oldFileName}`
);
this.fileType = data.type;

this.isShowingDiffs = ko.observable(false);
this.repoPath = args.repoPath;
this.server = args.server;
this.sha1 = args.sha1;
this.textDiffType = args.textDiffType;
this.wordWrap = args.wordWrap;
this.whiteSpace = args.whiteSpace;
Expand All @@ -26,11 +47,17 @@ class CommitLineDiff {
oldFilename: this.oldFileName(),
repoPath: this.repoPath,
server: this.server,
sha1: this.sha1,

diffKey: this.diffKey,
idx: this.idx,
textDiffType: this.textDiffType,
isShowingDiffs: this.isShowingDiffs,
whiteSpace: this.whiteSpace,
wordWrap: this.wordWrap,

isNew: this.isNew,
removed: this.wasRemoved,
conflict: this.hasConflict,
});
}

Expand Down
Loading