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:
Michael Cartner
2024-01-26 11:52:55 +01:00
commit b9c60b07dc
2789 changed files with 868795 additions and 0 deletions

View 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);
}
}