git-chopstick-core 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/git/exec.js.map +1 -1
- package/dist/lib/fatal-error.d.ts +1 -1
- package/dist/lib/fatal-error.js +1 -1
- package/dist/lib/fatal-error.js.map +1 -1
- package/dist/lib/progress/from-process.js.map +1 -1
- package/dist/lib/progress/index.d.ts +5 -5
- package/dist/lib/progress/index.js +5 -5
- package/dist/lib/progress/index.js.map +1 -1
- package/dist/lib/status-parser.js +0 -12
- package/dist/lib/status-parser.js.map +1 -1
- package/package.json +38 -8
- package/src/git/add.ts +0 -16
- package/src/git/apply.ts +0 -154
- package/src/git/authentication.ts +0 -20
- package/src/git/branch.ts +0 -206
- package/src/git/checkout-index.ts +0 -40
- package/src/git/checkout.ts +0 -235
- package/src/git/cherry-pick.ts +0 -504
- package/src/git/clean.ts +0 -9
- package/src/git/clone.ts +0 -86
- package/src/git/coerce-to-buffer.ts +0 -4
- package/src/git/coerce-to-string.ts +0 -4
- package/src/git/commit.ts +0 -136
- package/src/git/config.ts +0 -392
- package/src/git/core.ts +0 -625
- package/src/git/create-tail-stream.ts +0 -36
- package/src/git/credential.ts +0 -83
- package/src/git/description.ts +0 -33
- package/src/git/diff-check.ts +0 -27
- package/src/git/diff-index.ts +0 -116
- package/src/git/diff.ts +0 -880
- package/src/git/environment.ts +0 -116
- package/src/git/exec.ts +0 -285
- package/src/git/fetch.ts +0 -141
- package/src/git/for-each-ref.ts +0 -160
- package/src/git/format-patch.ts +0 -17
- package/src/git/git-delimiter-parser.ts +0 -95
- package/src/git/gitignore.ts +0 -157
- package/src/git/index.ts +0 -46
- package/src/git/init.ts +0 -11
- package/src/git/interpret-trailers.ts +0 -176
- package/src/git/lfs.ts +0 -100
- package/src/git/log.ts +0 -376
- package/src/git/merge-tree.ts +0 -42
- package/src/git/merge.ts +0 -154
- package/src/git/multi-operation-terminal-output.ts +0 -68
- package/src/git/pull.ts +0 -130
- package/src/git/push-terminal-chunk.ts +0 -41
- package/src/git/push.ts +0 -119
- package/src/git/rebase.ts +0 -627
- package/src/git/reflog.ts +0 -127
- package/src/git/refs.ts +0 -63
- package/src/git/remote.ts +0 -143
- package/src/git/reorder.ts +0 -153
- package/src/git/reset.ts +0 -101
- package/src/git/rev-list.ts +0 -201
- package/src/git/rev-parse.ts +0 -92
- package/src/git/revert.ts +0 -55
- package/src/git/rm.ts +0 -31
- package/src/git/show.ts +0 -88
- package/src/git/spawn.ts +0 -38
- package/src/git/squash.ts +0 -173
- package/src/git/stage.ts +0 -97
- package/src/git/stash.ts +0 -302
- package/src/git/status.ts +0 -502
- package/src/git/submodule.ts +0 -212
- package/src/git/tag.ts +0 -134
- package/src/git/update-index.ts +0 -169
- package/src/git/update-ref.ts +0 -50
- package/src/git/var.ts +0 -42
- package/src/git/worktree-include.ts +0 -146
- package/src/git/worktree.ts +0 -219
- package/src/index.ts +0 -11
- package/src/lib/api.ts +0 -7
- package/src/lib/diff-parser.ts +0 -249
- package/src/lib/directory-exists.ts +0 -10
- package/src/lib/errno-exception.ts +0 -12
- package/src/lib/fatal-error.ts +0 -23
- package/src/lib/feature-flag.ts +0 -29
- package/src/lib/file-system.ts +0 -7
- package/src/lib/get-old-path.ts +0 -11
- package/src/lib/git/environment.ts +0 -14
- package/src/lib/git-perf.ts +0 -3
- package/src/lib/helpers/default-branch.ts +0 -3
- package/src/lib/helpers/path.ts +0 -5
- package/src/lib/hooks/with-hooks-env.ts +0 -7
- package/src/lib/merge.ts +0 -3
- package/src/lib/noop.ts +0 -1
- package/src/lib/patch-formatter.ts +0 -18
- package/src/lib/path-exists.ts +0 -7
- package/src/lib/progress/from-process.ts +0 -10
- package/src/lib/progress/index.ts +0 -43
- package/src/lib/progress/revert.ts +0 -17
- package/src/lib/rebase.ts +0 -3
- package/src/lib/remove-remote-prefix.ts +0 -4
- package/src/lib/resolve-git-proxy.ts +0 -3
- package/src/lib/round.ts +0 -4
- package/src/lib/split-buffer.ts +0 -14
- package/src/lib/status-parser.ts +0 -188
- package/src/lib/stores/helpers/find-default-remote.ts +0 -3
- package/src/lib/trampoline/trampoline-environment.ts +0 -8
- package/src/models/branch.ts +0 -78
- package/src/models/cherry-pick.ts +0 -12
- package/src/models/clone-options.ts +0 -6
- package/src/models/commit-identity.ts +0 -35
- package/src/models/commit.ts +0 -44
- package/src/models/computed-action.ts +0 -6
- package/src/models/diff/diff-data.ts +0 -78
- package/src/models/diff/diff-line.ts +0 -36
- package/src/models/diff/diff-selection.ts +0 -165
- package/src/models/diff/image-diff.ts +0 -6
- package/src/models/diff/image.ts +0 -8
- package/src/models/diff/index.ts +0 -6
- package/src/models/diff/raw-diff.ts +0 -41
- package/src/models/git-author.ts +0 -16
- package/src/models/index.ts +0 -36
- package/src/models/manual-conflict-resolution.ts +0 -4
- package/src/models/merge.ts +0 -6
- package/src/models/multi-commit-operation.ts +0 -6
- package/src/models/progress.ts +0 -67
- package/src/models/rebase.ts +0 -20
- package/src/models/remote.ts +0 -10
- package/src/models/repository.ts +0 -16
- package/src/models/stash-entry.ts +0 -25
- package/src/models/status.ts +0 -275
- package/src/models/submodule.ts +0 -13
- package/src/models/worktree.ts +0 -11
package/src/git/tag.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import { git } from './core.js'
|
|
2
|
-
import { Repository } from '../models/repository.js'
|
|
3
|
-
import { IRemote } from '../models/remote.js'
|
|
4
|
-
import { envForRemoteOperation } from './environment.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Create a new tag on the given target commit.
|
|
8
|
-
*
|
|
9
|
-
* @param repository - The repository in which to create the new tag.
|
|
10
|
-
* @param name - The name of the new tag.
|
|
11
|
-
* @param targetCommitSha - The SHA of the commit where the new tag will live on.
|
|
12
|
-
*/
|
|
13
|
-
export async function createTag(
|
|
14
|
-
repository: Repository,
|
|
15
|
-
name: string,
|
|
16
|
-
targetCommitSha: string
|
|
17
|
-
): Promise<void> {
|
|
18
|
-
const args = ['tag', '-a', '-m', '', name, targetCommitSha]
|
|
19
|
-
|
|
20
|
-
await git(args, repository.path, 'createTag')
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Delete a tag.
|
|
25
|
-
*
|
|
26
|
-
* @param repository - The repository in which to create the new tag.
|
|
27
|
-
* @param name - The name of the tag to delete.
|
|
28
|
-
*/
|
|
29
|
-
export async function deleteTag(
|
|
30
|
-
repository: Repository,
|
|
31
|
-
name: string
|
|
32
|
-
): Promise<void> {
|
|
33
|
-
const args = ['tag', '-d', name]
|
|
34
|
-
|
|
35
|
-
await git(args, repository.path, 'deleteTag')
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Gets all the local tags. Returns a Map with the tag name and the commit it points to.
|
|
40
|
-
*
|
|
41
|
-
* @param repository The repository in which to get all the tags from.
|
|
42
|
-
*/
|
|
43
|
-
export async function getAllTags(
|
|
44
|
-
repository: Repository
|
|
45
|
-
): Promise<Map<string, string>> {
|
|
46
|
-
const args = ['show-ref', '--tags', '-d']
|
|
47
|
-
|
|
48
|
-
const tags = await git(args, repository.path, 'getAllTags', {
|
|
49
|
-
successExitCodes: new Set([0, 1]), // when there are no tags, git exits with 1.
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
const tagsArray: Array<[string, string]> = tags.stdout
|
|
53
|
-
.split('\n')
|
|
54
|
-
.filter(line => line !== '')
|
|
55
|
-
.map(line => {
|
|
56
|
-
const [commitSha, rawTagName] = line.split(' ')
|
|
57
|
-
|
|
58
|
-
// Normalize tag names by removing the leading ref/tags/ and the trailing ^{}.
|
|
59
|
-
//
|
|
60
|
-
// git show-ref returns two entries for annotated tags:
|
|
61
|
-
// deadbeef refs/tags/annotated-tag
|
|
62
|
-
// de510b99 refs/tags/annotated-tag^{}
|
|
63
|
-
//
|
|
64
|
-
// The first entry sha correspond to the blob object of the annotation, while the second
|
|
65
|
-
// entry corresponds to the actual commit where the tag was created.
|
|
66
|
-
// By normalizing the tag name we can make sure that the commit sha gets stored in the returned
|
|
67
|
-
// Map of commits (since git will always print the entry with the commit sha at the end).
|
|
68
|
-
const tagName = rawTagName
|
|
69
|
-
.replace(/^refs\/tags\//, '')
|
|
70
|
-
.replace(/\^\{\}$/, '')
|
|
71
|
-
|
|
72
|
-
return [tagName, commitSha]
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
return new Map(tagsArray)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Fetches the tags that will get pushed to the remote repository (it does a network request).
|
|
80
|
-
*
|
|
81
|
-
* @param repository - The repository in which to check for unpushed tags
|
|
82
|
-
* @param account - The account to use when authenticating with the remote
|
|
83
|
-
* @param remote - The remote to check for unpushed tags
|
|
84
|
-
* @param branchName - The branch that will be used on the push command
|
|
85
|
-
*/
|
|
86
|
-
export async function fetchTagsToPush(
|
|
87
|
-
repository: Repository,
|
|
88
|
-
remote: IRemote,
|
|
89
|
-
branchName: string
|
|
90
|
-
): Promise<ReadonlyArray<string>> {
|
|
91
|
-
const args = [
|
|
92
|
-
'push',
|
|
93
|
-
remote.name,
|
|
94
|
-
branchName,
|
|
95
|
-
'--follow-tags',
|
|
96
|
-
'--dry-run',
|
|
97
|
-
'--no-verify',
|
|
98
|
-
'--porcelain',
|
|
99
|
-
]
|
|
100
|
-
|
|
101
|
-
const result = await git(args, repository.path, 'fetchTagsToPush', {
|
|
102
|
-
env: await envForRemoteOperation(remote.url),
|
|
103
|
-
successExitCodes: new Set([0, 1, 128]),
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
107
|
-
// Only when the exit code of git is 0 or 1, its stdout is parseable.
|
|
108
|
-
// In other cases, we just rethrow the error so our memoization layer
|
|
109
|
-
// doesn't cache it indefinitely.
|
|
110
|
-
throw result.gitError
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const lines = result.stdout.split('\n')
|
|
114
|
-
let currentLine = 1
|
|
115
|
-
const unpushedTags = []
|
|
116
|
-
|
|
117
|
-
// the last line of this porcelain command is always 'Done'
|
|
118
|
-
while (currentLine < lines.length && lines[currentLine] !== 'Done') {
|
|
119
|
-
const line = lines[currentLine]
|
|
120
|
-
const parts = line.split('\t')
|
|
121
|
-
|
|
122
|
-
if (parts[0] === '*' && parts[2] === '[new tag]') {
|
|
123
|
-
const [tagName] = parts[1].split(':')
|
|
124
|
-
|
|
125
|
-
if (tagName !== undefined) {
|
|
126
|
-
unpushedTags.push(tagName.replace(/^refs\/tags\//, ''))
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
currentLine++
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return unpushedTags
|
|
134
|
-
}
|
package/src/git/update-index.ts
DELETED
|
@@ -1,169 +0,0 @@
|
|
|
1
|
-
import { git } from './core.js'
|
|
2
|
-
import { Repository } from '../models/repository.js'
|
|
3
|
-
import { DiffSelectionType } from '../models/diff/index.js'
|
|
4
|
-
import { applyPatchToIndex } from './apply.js'
|
|
5
|
-
import {
|
|
6
|
-
WorkingDirectoryFileChange,
|
|
7
|
-
AppFileStatusKind,
|
|
8
|
-
} from '../models/status.js'
|
|
9
|
-
|
|
10
|
-
interface IUpdateIndexOptions {
|
|
11
|
-
/**
|
|
12
|
-
* Whether or not to add a file when it exists in the working directory
|
|
13
|
-
* but not in the index. Defaults to true (note that this differs from the
|
|
14
|
-
* default behavior of Git which is to ignore new files).
|
|
15
|
-
*
|
|
16
|
-
* @default true
|
|
17
|
-
*/
|
|
18
|
-
add?: boolean
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Whether or not to remove a file when it exists in the index but not
|
|
22
|
-
* in the working directory. Defaults to true (note that this differs from
|
|
23
|
-
* the default behavior of Git which is to ignore removed files).
|
|
24
|
-
*
|
|
25
|
-
* @default true
|
|
26
|
-
*/
|
|
27
|
-
remove?: boolean
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Whether or not to forcefully remove a file from the index even though it
|
|
31
|
-
* exists in the working directory. This implies remove.
|
|
32
|
-
*
|
|
33
|
-
* @default false
|
|
34
|
-
*/
|
|
35
|
-
forceRemove?: boolean
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Whether or not to replace conflicting entries in the index with that of
|
|
39
|
-
* the working directory. Imagine the following scenario
|
|
40
|
-
*
|
|
41
|
-
* $ touch foo && git update-index --add foo && git commit -m 'foo'
|
|
42
|
-
* $ rm foo && mkdir foo && echo "bar" > foo/bar
|
|
43
|
-
* $ git update-index --add foo/bar
|
|
44
|
-
* error: 'foo/bar' appears as both a file and as a directory
|
|
45
|
-
* error: foo/bar: cannot add to the index - missing --add option?
|
|
46
|
-
* fatal: Unable to process path foo/bar
|
|
47
|
-
*
|
|
48
|
-
* Replace ignores this conflict and overwrites the index with the
|
|
49
|
-
* newly created directory, causing the original foo file to be deleted
|
|
50
|
-
* in the index. This behavior matches what `git add` would do in a similar
|
|
51
|
-
* scenario.
|
|
52
|
-
*
|
|
53
|
-
* @default true
|
|
54
|
-
*/
|
|
55
|
-
replace?: boolean
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Updates the index with file contents from the working tree. This method
|
|
60
|
-
* is a noop when no paths are provided.
|
|
61
|
-
*
|
|
62
|
-
* @param paths A list of paths which are to be updated with file contents and
|
|
63
|
-
* status from the working directory.
|
|
64
|
-
*
|
|
65
|
-
* @param options See the IUpdateIndexOptions interface for more details.
|
|
66
|
-
*/
|
|
67
|
-
async function updateIndex(
|
|
68
|
-
repository: Repository,
|
|
69
|
-
paths: ReadonlyArray<string>,
|
|
70
|
-
options: IUpdateIndexOptions = {}
|
|
71
|
-
) {
|
|
72
|
-
if (paths.length === 0) {
|
|
73
|
-
return
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const args = ['update-index']
|
|
77
|
-
|
|
78
|
-
if (options.add !== false) {
|
|
79
|
-
args.push('--add')
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
if (options.remove !== false || options.forceRemove === true) {
|
|
83
|
-
args.push('--remove')
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (options.forceRemove) {
|
|
87
|
-
args.push('--force-remove')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
if (options.replace !== false) {
|
|
91
|
-
args.push('--replace')
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
args.push('-z', '--stdin')
|
|
95
|
-
|
|
96
|
-
await git(args, repository.path, 'updateIndex', {
|
|
97
|
-
stdin: paths.join('\0'),
|
|
98
|
-
})
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Stage all the given files by either staging the entire path or by applying
|
|
103
|
-
* a patch.
|
|
104
|
-
*
|
|
105
|
-
* Note that prior to stageFiles the index has been completely reset,
|
|
106
|
-
* the job of this function is to set up the index in such a way that it
|
|
107
|
-
* reflects what the user has selected in the app.
|
|
108
|
-
*/
|
|
109
|
-
export async function stageFiles(
|
|
110
|
-
repository: Repository,
|
|
111
|
-
files: ReadonlyArray<WorkingDirectoryFileChange>
|
|
112
|
-
): Promise<void> {
|
|
113
|
-
const normal = []
|
|
114
|
-
const oldRenamed = []
|
|
115
|
-
const partial = []
|
|
116
|
-
const deletedFiles = []
|
|
117
|
-
|
|
118
|
-
for (const file of files) {
|
|
119
|
-
if (file.selection.getSelectionType() === DiffSelectionType.All) {
|
|
120
|
-
normal.push(file.path)
|
|
121
|
-
if (file.status.kind === AppFileStatusKind.Renamed) {
|
|
122
|
-
oldRenamed.push(file.status.oldPath)
|
|
123
|
-
} else if (file.status.kind === AppFileStatusKind.Deleted) {
|
|
124
|
-
deletedFiles.push(file.path)
|
|
125
|
-
}
|
|
126
|
-
} else {
|
|
127
|
-
partial.push(file)
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Staging files happens in three steps.
|
|
132
|
-
//
|
|
133
|
-
// In the first step we run through all of the renamed files, or
|
|
134
|
-
// more specifically the source files (old) that were renamed and
|
|
135
|
-
// forcefully remove them from the index. We do this in order to handle
|
|
136
|
-
// the scenario where a file has been renamed and a new file has been
|
|
137
|
-
// created in its original position. Think of it like this
|
|
138
|
-
//
|
|
139
|
-
// $ touch foo && git add foo && git commit -m 'foo'
|
|
140
|
-
// $ git mv foo bar
|
|
141
|
-
// $ echo "I'm a new foo" > foo
|
|
142
|
-
//
|
|
143
|
-
// Now we have a file which is of type Renamed that has its path set
|
|
144
|
-
// to 'bar' and its oldPath set to 'foo'. But there's a new file called
|
|
145
|
-
// foo in the repository. So if the user selects the 'foo -> bar' change
|
|
146
|
-
// but not the new 'foo' file for inclusion in this commit we don't
|
|
147
|
-
// want to add the new 'foo', we just want to recreate the move in the
|
|
148
|
-
// index. We do this by forcefully removing the old path from the index
|
|
149
|
-
// and then later (in step 2) stage the new file.
|
|
150
|
-
await updateIndex(repository, oldRenamed, { forceRemove: true })
|
|
151
|
-
|
|
152
|
-
// In the second step we update the index to match
|
|
153
|
-
// the working directory in the case of new, modified, deleted,
|
|
154
|
-
// and copied files as well as the destination paths for renamed
|
|
155
|
-
// paths.
|
|
156
|
-
await updateIndex(repository, normal)
|
|
157
|
-
|
|
158
|
-
// This third step will only happen if we have files that have been marked
|
|
159
|
-
// for deletion. This covers us for files that were blown away in the last
|
|
160
|
-
// updateIndex call
|
|
161
|
-
await updateIndex(repository, deletedFiles, { forceRemove: true })
|
|
162
|
-
|
|
163
|
-
// Finally we run through all files that have partial selections.
|
|
164
|
-
// We don't care about renamed or not here since applyPatchToIndex
|
|
165
|
-
// has logic to support that scenario.
|
|
166
|
-
for (const file of partial) {
|
|
167
|
-
await applyPatchToIndex(repository, file)
|
|
168
|
-
}
|
|
169
|
-
}
|
package/src/git/update-ref.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { git } from './core.js'
|
|
2
|
-
import { Repository } from '../models/repository.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Update the ref to a new value.
|
|
6
|
-
*
|
|
7
|
-
* @param repository - The repository in which the ref exists.
|
|
8
|
-
* @param ref - The ref to update. Must be fully qualified
|
|
9
|
-
* (e.g., `refs/heads/NAME`).
|
|
10
|
-
* @param oldValue - The value we expect the ref to have currently. If it
|
|
11
|
-
* doesn't match, the update will be aborted.
|
|
12
|
-
* @param newValue - The new value for the ref.
|
|
13
|
-
* @param reason - The reflog entry.
|
|
14
|
-
*/
|
|
15
|
-
export async function updateRef(
|
|
16
|
-
repository: Repository,
|
|
17
|
-
ref: string,
|
|
18
|
-
oldValue: string,
|
|
19
|
-
newValue: string,
|
|
20
|
-
reason: string
|
|
21
|
-
): Promise<void> {
|
|
22
|
-
await git(
|
|
23
|
-
['update-ref', ref, newValue, oldValue, '-m', reason],
|
|
24
|
-
repository.path,
|
|
25
|
-
'updateRef'
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Remove a ref.
|
|
31
|
-
*
|
|
32
|
-
* @param repository - The repository in which the ref exists.
|
|
33
|
-
* @param ref - The ref to remove. Should be fully qualified, but may also be 'HEAD'.
|
|
34
|
-
* @param reason - The reflog entry (optional). Note that this is only useful when
|
|
35
|
-
* deleting the HEAD reference as deleting any other reference will
|
|
36
|
-
* implicitly delete the reflog file for that reference as well.
|
|
37
|
-
*/
|
|
38
|
-
export async function deleteRef(
|
|
39
|
-
repository: Repository,
|
|
40
|
-
ref: string,
|
|
41
|
-
reason?: string
|
|
42
|
-
) {
|
|
43
|
-
const args = ['update-ref', '-d', ref]
|
|
44
|
-
|
|
45
|
-
if (reason !== undefined) {
|
|
46
|
-
args.push('-m', reason)
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
await git(args, repository.path, 'deleteRef')
|
|
50
|
-
}
|
package/src/git/var.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { git } from './core.js'
|
|
2
|
-
import { Repository } from '../models/repository.js'
|
|
3
|
-
import { CommitIdentity } from '../models/commit-identity.js'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Gets the author identity, ie the name and email which would
|
|
7
|
-
* have been used should a commit have been performed in this
|
|
8
|
-
* instance. This differs from what's stored in the user.name
|
|
9
|
-
* and user.email config variables in that it will match what
|
|
10
|
-
* Git itself will use in a commit even if there's no name or
|
|
11
|
-
* email configured. If no email or name is configured Git will
|
|
12
|
-
* attempt to come up with a suitable replacement using the
|
|
13
|
-
* signed-in system user and hostname.
|
|
14
|
-
*
|
|
15
|
-
* A null return value means that no name/and or email was set
|
|
16
|
-
* and the user.useconfigonly setting prevented Git from making
|
|
17
|
-
* up a user ident string. If this returns null any subsequent
|
|
18
|
-
* commits can be expected to fail as well.
|
|
19
|
-
*/
|
|
20
|
-
export async function getAuthorIdentity(
|
|
21
|
-
repository: Repository
|
|
22
|
-
): Promise<CommitIdentity | null> {
|
|
23
|
-
const result = await git(
|
|
24
|
-
['var', 'GIT_AUTHOR_IDENT'],
|
|
25
|
-
repository.path,
|
|
26
|
-
'getAuthorIdentity',
|
|
27
|
-
{
|
|
28
|
-
successExitCodes: new Set([0, 128]),
|
|
29
|
-
}
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
// If user.user.useconfigonly is set and no user.name or user.email
|
|
33
|
-
if (result.exitCode === 128) {
|
|
34
|
-
return null
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
try {
|
|
38
|
-
return CommitIdentity.parseIdentity(result.stdout)
|
|
39
|
-
} catch (err) {
|
|
40
|
-
return null
|
|
41
|
-
}
|
|
42
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import * as Fs from 'fs'
|
|
2
|
-
import * as Path from 'path'
|
|
3
|
-
import { readFile, copyFile, mkdir } from 'fs/promises'
|
|
4
|
-
import ignore from 'ignore'
|
|
5
|
-
import type { Repository } from '../models/repository.js'
|
|
6
|
-
import { git } from './core.js'
|
|
7
|
-
import { addWorktree, getMainWorktreePath } from './worktree.js'
|
|
8
|
-
|
|
9
|
-
const WorktreeIncludeFile = '.worktreeinclude'
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Reads the patterns from the `.worktreeinclude` file at the root of the
|
|
13
|
-
* given repository path.
|
|
14
|
-
*
|
|
15
|
-
* The file uses `.gitignore` syntax. Blank lines and lines starting with `#`
|
|
16
|
-
* are ignored.
|
|
17
|
-
*
|
|
18
|
-
* Returns an empty array if the file does not exist.
|
|
19
|
-
*/
|
|
20
|
-
export async function readWorktreeIncludePatterns(
|
|
21
|
-
repositoryPath: string
|
|
22
|
-
): Promise<ReadonlyArray<string>> {
|
|
23
|
-
const filePath = Path.join(repositoryPath, WorktreeIncludeFile)
|
|
24
|
-
|
|
25
|
-
let contents: string
|
|
26
|
-
try {
|
|
27
|
-
contents = await readFile(filePath, 'utf8')
|
|
28
|
-
} catch {
|
|
29
|
-
return []
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return contents
|
|
33
|
-
.split('\n')
|
|
34
|
-
.map(line => line.trim())
|
|
35
|
-
.filter(line => line.length > 0 && !line.startsWith('#'))
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Returns the list of gitignored files in `repositoryPath` that match any of
|
|
40
|
-
* the given patterns.
|
|
41
|
-
*
|
|
42
|
-
* Only files that are both gitignored **and** matched by a `.worktreeinclude`
|
|
43
|
-
* pattern are returned — tracked files are never included.
|
|
44
|
-
*/
|
|
45
|
-
export async function getIgnoredFilesMatchingPatterns(
|
|
46
|
-
repository: Repository,
|
|
47
|
-
patterns: ReadonlyArray<string>
|
|
48
|
-
): Promise<ReadonlyArray<string>> {
|
|
49
|
-
if (patterns.length === 0) {
|
|
50
|
-
return []
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const result = await git(
|
|
54
|
-
['ls-files', '--others', '--ignored', '--exclude-standard', '-z'],
|
|
55
|
-
repository.path,
|
|
56
|
-
'getIgnoredFiles'
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
// Files are NUL-separated; filter out empty entries from the split
|
|
60
|
-
const ignoredFiles = result.stdout.split('\0').filter(f => f.length > 0)
|
|
61
|
-
|
|
62
|
-
const ig = ignore().add(patterns)
|
|
63
|
-
return ignoredFiles.filter(f => ig.ignores(f))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Copies each file in `files` (relative paths) from `sourcePath` to
|
|
68
|
-
* `destinationPath`, preserving the directory structure.
|
|
69
|
-
*
|
|
70
|
-
* Files that cannot be copied (e.g. because they no longer exist at the
|
|
71
|
-
* source) are skipped silently — a failure to copy a single file never
|
|
72
|
-
* prevents the others from being copied.
|
|
73
|
-
*/
|
|
74
|
-
export async function copyWorktreeIncludeFiles(
|
|
75
|
-
sourcePath: string,
|
|
76
|
-
destinationPath: string,
|
|
77
|
-
files: ReadonlyArray<string>
|
|
78
|
-
): Promise<void> {
|
|
79
|
-
for (const file of files) {
|
|
80
|
-
const src = Path.join(sourcePath, file)
|
|
81
|
-
const dest = Path.join(destinationPath, file)
|
|
82
|
-
|
|
83
|
-
// Guard against path traversal: the resolved destination must be
|
|
84
|
-
// inside the worktree directory.
|
|
85
|
-
const resolvedDest = Path.resolve(dest)
|
|
86
|
-
const resolvedWorktreeRoot = Path.resolve(destinationPath)
|
|
87
|
-
if (!resolvedDest.startsWith(resolvedWorktreeRoot + Path.sep)) {
|
|
88
|
-
continue
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
try {
|
|
92
|
-
// eslint-disable-next-line no-sync
|
|
93
|
-
if (!Fs.existsSync(src)) {
|
|
94
|
-
continue
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
await mkdir(Path.dirname(dest), { recursive: true })
|
|
98
|
-
await copyFile(src, dest)
|
|
99
|
-
} catch (e) {
|
|
100
|
-
console.warn(
|
|
101
|
-
`[worktree-include] Failed to copy '${file}' to worktree`,
|
|
102
|
-
e instanceof Error ? e : undefined
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Creates a new git worktree and then copies any gitignored files listed in
|
|
110
|
-
* the `.worktreeinclude` file from the main worktree into the newly created
|
|
111
|
-
* worktree.
|
|
112
|
-
*
|
|
113
|
-
* The copy step is best-effort: failures are logged but do not prevent the
|
|
114
|
-
* worktree from being used.
|
|
115
|
-
*
|
|
116
|
-
* @param repository The repository to create the worktree in.
|
|
117
|
-
* @param path The absolute path where the new worktree should be created.
|
|
118
|
-
* @param options Options forwarded to `addWorktree`.
|
|
119
|
-
*/
|
|
120
|
-
export async function addWorktreeWithIncludes(
|
|
121
|
-
repository: Repository,
|
|
122
|
-
path: string,
|
|
123
|
-
options: Parameters<typeof addWorktree>[2] = {}
|
|
124
|
-
): Promise<void> {
|
|
125
|
-
await addWorktree(repository, path, options)
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const mainPath = (await getMainWorktreePath(repository)) ?? repository.path
|
|
129
|
-
const patterns = await readWorktreeIncludePatterns(mainPath)
|
|
130
|
-
|
|
131
|
-
if (patterns.length === 0) {
|
|
132
|
-
return
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const files = await getIgnoredFilesMatchingPatterns(repository, patterns)
|
|
136
|
-
|
|
137
|
-
if (files.length > 0) {
|
|
138
|
-
await copyWorktreeIncludeFiles(mainPath, path, files)
|
|
139
|
-
}
|
|
140
|
-
} catch (e) {
|
|
141
|
-
console.warn(
|
|
142
|
-
'[worktree-include] Failed to process .worktreeinclude; worktree was still created',
|
|
143
|
-
e instanceof Error ? e : undefined
|
|
144
|
-
)
|
|
145
|
-
}
|
|
146
|
-
}
|