mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-14 08:12:55 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
158
packages/noodl-git/src/core/update-index.ts
Normal file
158
packages/noodl-git/src/core/update-index.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { git } from './client';
|
||||
import { DiffSelectionType } from './models/diff';
|
||||
import { applyPatchToIndex } from './apply';
|
||||
import { WorkingDirectoryFileChange, FileStatusKind } from './models/status';
|
||||
|
||||
interface IUpdateIndexOptions {
|
||||
/**
|
||||
* Whether or not to add a file when it exists in the working directory
|
||||
* but not in the index. Defaults to true (note that this differs from the
|
||||
* default behavior of Git which is to ignore new files).
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
add?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not to remove a file when it exists in the index but not
|
||||
* in the working directory. Defaults to true (note that this differs from
|
||||
* the default behavior of Git which is to ignore removed files).
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
remove?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not to forcefully remove a file from the index even though it
|
||||
* exists in the working directory. This implies remove.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceRemove?: boolean;
|
||||
|
||||
/**
|
||||
* Whether or not to replace conflicting entries in the index with that of
|
||||
* the working directory. Imagine the following scenario
|
||||
*
|
||||
* $ touch foo && git update-index --add foo && git commit -m 'foo'
|
||||
* $ rm foo && mkdir foo && echo "bar" > foo/bar
|
||||
* $ git update-index --add foo/bar
|
||||
* error: 'foo/bar' appears as both a file and as a directory
|
||||
* error: foo/bar: cannot add to the index - missing --add option?
|
||||
* fatal: Unable to process path foo/bar
|
||||
*
|
||||
* Replace ignores this conflict and overwrites the index with the
|
||||
* newly created directory, causing the original foo file to be deleted
|
||||
* in the index. This behavior matches what `git add` would do in a similar
|
||||
* scenario.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
replace?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the index with file contents from the working tree. This method
|
||||
* is a noop when no paths are provided.
|
||||
*
|
||||
* @param paths A list of paths which are to be updated with file contents and
|
||||
* status from the working directory.
|
||||
*
|
||||
* @param options See the IUpdateIndexOptions interface for more details.
|
||||
*/
|
||||
async function updateIndex(filePath: string, paths: ReadonlyArray<string>, options: IUpdateIndexOptions = {}) {
|
||||
if (paths.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const args = ['update-index'];
|
||||
|
||||
if (options.add !== false) {
|
||||
args.push('--add');
|
||||
}
|
||||
|
||||
if (options.remove !== false || options.forceRemove === true) {
|
||||
args.push('--remove');
|
||||
}
|
||||
|
||||
if (options.forceRemove) {
|
||||
args.push('--force-remove');
|
||||
}
|
||||
|
||||
if (options.replace !== false) {
|
||||
args.push('--replace');
|
||||
}
|
||||
|
||||
args.push('-z', '--stdin');
|
||||
|
||||
await git(args, filePath, 'updateIndex', {
|
||||
stdin: paths.join('\0')
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stage all the given files by either staging the entire path or by applying
|
||||
* a patch.
|
||||
*
|
||||
* Note that prior to stageFiles the index has been completely reset,
|
||||
* the job of this function is to set up the index in such a way that it
|
||||
* reflects what the user has selected in the app.
|
||||
*/
|
||||
export async function stageFiles(filePath: string, files: ReadonlyArray<WorkingDirectoryFileChange>): Promise<void> {
|
||||
const normal = [];
|
||||
const oldRenamed = [];
|
||||
const partial = [];
|
||||
const deletedFiles = [];
|
||||
|
||||
for (const file of files) {
|
||||
if (file.selection.getSelectionType() === DiffSelectionType.All) {
|
||||
normal.push(file.path);
|
||||
if (file.status.kind === FileStatusKind.Renamed) {
|
||||
oldRenamed.push(file.status.oldPath);
|
||||
} else if (file.status.kind === FileStatusKind.Deleted) {
|
||||
deletedFiles.push(file.path);
|
||||
}
|
||||
} else {
|
||||
partial.push(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Staging files happens in three steps.
|
||||
//
|
||||
// In the first step we run through all of the renamed files, or
|
||||
// more specifically the source files (old) that were renamed and
|
||||
// forcefully remove them from the index. We do this in order to handle
|
||||
// the scenario where a file has been renamed and a new file has been
|
||||
// created in its original position. Think of it like this
|
||||
//
|
||||
// $ touch foo && git add foo && git commit -m 'foo'
|
||||
// $ git mv foo bar
|
||||
// $ echo "I'm a new foo" > foo
|
||||
//
|
||||
// Now we have a file which is of type Renamed that has its path set
|
||||
// to 'bar' and its oldPath set to 'foo'. But there's a new file called
|
||||
// foo in the repository. So if the user selects the 'foo -> bar' change
|
||||
// but not the new 'foo' file for inclusion in this commit we don't
|
||||
// want to add the new 'foo', we just want to recreate the move in the
|
||||
// index. We do this by forcefully removing the old path from the index
|
||||
// and then later (in step 2) stage the new file.
|
||||
await updateIndex(filePath, oldRenamed, { forceRemove: true });
|
||||
|
||||
// In the second step we update the index to match
|
||||
// the working directory in the case of new, modified, deleted,
|
||||
// and copied files as well as the destination paths for renamed
|
||||
// paths.
|
||||
await updateIndex(filePath, normal);
|
||||
|
||||
// This third step will only happen if we have files that have been marked
|
||||
// for deletion. This covers us for files that were blown away in the last
|
||||
// updateIndex call
|
||||
await updateIndex(filePath, deletedFiles, { forceRemove: true });
|
||||
|
||||
// Finally we run through all files that have partial selections.
|
||||
// We don't care about renamed or not here since applyPatchToIndex
|
||||
// has logic to support that scenario.
|
||||
for (const file of partial) {
|
||||
await applyPatchToIndex(filePath, file);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user