@amalgm/live 0.2.81-main.40832913201 → 0.2.81-main.40972490558
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/dist/git/chain.d.ts +6 -4
- package/dist/git/chain.d.ts.map +1 -1
- package/dist/git/chain.js +18 -15
- package/dist/git/chain.js.map +1 -1
- package/dist/git/index.d.ts +1 -0
- package/dist/git/index.d.ts.map +1 -1
- package/dist/git/index.js +1 -0
- package/dist/git/index.js.map +1 -1
- package/dist/git/merge.d.ts +95 -0
- package/dist/git/merge.d.ts.map +1 -0
- package/dist/git/merge.js +385 -0
- package/dist/git/merge.js.map +1 -0
- package/dist/git/plan.d.ts +2 -2
- package/dist/git/plan.js +2 -2
- package/dist/git/refs.d.ts +29 -0
- package/dist/git/refs.d.ts.map +1 -0
- package/dist/git/refs.js +65 -0
- package/dist/git/refs.js.map +1 -0
- package/dist-cjs/git/chain.js +19 -16
- package/dist-cjs/git/chain.js.map +1 -1
- package/dist-cjs/git/index.js +1 -0
- package/dist-cjs/git/index.js.map +1 -1
- package/dist-cjs/git/merge.js +392 -0
- package/dist-cjs/git/merge.js.map +1 -0
- package/dist-cjs/git/plan.js +2 -2
- package/dist-cjs/git/refs.js +75 -0
- package/dist-cjs/git/refs.js.map +1 -0
- package/package.json +4 -4
package/dist/git/chain.d.ts
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Files: an artifact carries only the blobs its parent's state lacks, so a
|
|
6
6
|
* file the receiver does not already hold is found by walking back to the
|
|
7
|
-
* artifact that first carried it
|
|
7
|
+
* artifact that first carried it, each artifact read once and only as far
|
|
8
|
+
* back as a file asked for first travelled.
|
|
8
9
|
*
|
|
9
10
|
* Objects: a pack is thin against its excluded roots, so it installs once
|
|
10
11
|
* everything they reach stands here; otherwise its parent's pack comes first,
|
|
@@ -35,9 +36,10 @@ export interface GitChainPacks {
|
|
|
35
36
|
export interface GitChainOwnerWait {
|
|
36
37
|
readonly kind: 'owner';
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
-
*
|
|
40
|
-
|
|
39
|
+
/** Reads blobs from the chain back from the target: each artifact read
|
|
40
|
+
* once, only as far back as a blob asked for first travelled. Null for a
|
|
41
|
+
* blob no artifact on the chain carries. */
|
|
42
|
+
export declare function gitChainReader(target: string, artifact: GitArtifactReader): (oid: string) => Promise<Uint8Array | null>;
|
|
41
43
|
/** The packs that bring the target's objects here. */
|
|
42
44
|
export declare function gitChainPacks(target: string, artifact: GitArtifactReader, holds: GitHolds): Promise<Checked<GitChainPacks | GitChainOwnerWait>>;
|
|
43
45
|
//# sourceMappingURL=chain.d.ts.map
|
package/dist/git/chain.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../../src/git/chain.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"chain.d.ts","sourceRoot":"","sources":["../../src/git/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,sDAAsD;AACtD,MAAM,MAAM,iBAAiB,GAAG,CAAC,gBAAgB,EAAE,MAAM,KAAK,OAAO,CAAC,WAAW,CAAC,CAAC;AAEnF,wEAAwE;AACxE,MAAM,MAAM,QAAQ,GAAG,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;AAErE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,gEAAgE;IAChE,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED;mBACmB;AACnB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AAED;;4CAE4C;AAC5C,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAWvH;AAED,sDAAsD;AACtD,wBAAsB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,EAC7E,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,GAAG,iBAAiB,CAAC,CAAC,CAUtE"}
|
package/dist/git/chain.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Files: an artifact carries only the blobs its parent's state lacks, so a
|
|
6
6
|
* file the receiver does not already hold is found by walking back to the
|
|
7
|
-
* artifact that first carried it
|
|
7
|
+
* artifact that first carried it, each artifact read once and only as far
|
|
8
|
+
* back as a file asked for first travelled.
|
|
8
9
|
*
|
|
9
10
|
* Objects: a pack is thin against its excluded roots, so it installs once
|
|
10
11
|
* everything they reach stands here; otherwise its parent's pack comes first,
|
|
@@ -20,20 +21,22 @@
|
|
|
20
21
|
* then asks its Git which packs it lacks.
|
|
21
22
|
*/
|
|
22
23
|
import { fail, pass } from '../contracts/validate.js';
|
|
23
|
-
/**
|
|
24
|
-
*
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
24
|
+
/** Reads blobs from the chain back from the target: each artifact read
|
|
25
|
+
* once, only as far back as a blob asked for first travelled. Null for a
|
|
26
|
+
* blob no artifact on the chain carries. */
|
|
27
|
+
export function gitChainReader(target, artifact) {
|
|
28
|
+
const found = new Map();
|
|
29
|
+
let next = target;
|
|
30
|
+
return async (oid) => {
|
|
31
|
+
while (!found.has(oid) && next !== null) {
|
|
32
|
+
const read = await artifact(next);
|
|
33
|
+
for (const [blob, bytes] of read.blobs)
|
|
34
|
+
if (!found.has(blob))
|
|
35
|
+
found.set(blob, bytes);
|
|
36
|
+
next = read.parent;
|
|
37
|
+
}
|
|
38
|
+
return found.get(oid) ?? null;
|
|
39
|
+
};
|
|
37
40
|
}
|
|
38
41
|
/** The packs that bring the target's objects here. */
|
|
39
42
|
export async function gitChainPacks(target, artifact, holds) {
|
package/dist/git/chain.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chain.js","sourceRoot":"","sources":["../../src/git/chain.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"chain.js","sourceRoot":"","sources":["../../src/git/chain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,IAAI,EAAE,IAAI,EAAgB,MAAM,0BAA0B,CAAC;AAqBpE;;4CAE4C;AAC5C,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,QAA2B;IACxE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAsB,CAAC;IAC5C,IAAI,IAAI,GAAkB,MAAM,CAAC;IACjC,OAAO,KAAK,EAAC,GAAG,EAAC,EAAE;QACjB,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YACxC,MAAM,IAAI,GAAgB,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK;gBAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;YACrF,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;QACrB,CAAC;QACD,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IAChC,CAAC,CAAC;AACJ,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,MAAc,EAAE,QAA2B,EAC7E,KAAe;IACf,MAAM,KAAK,GAAiB,EAAE,CAAC;IAC/B,KAAK,IAAI,IAAI,GAAkB,MAAM,EAAE,IAAI,KAAK,IAAI,GAAG,CAAC;QACtD,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC;QACxF,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QACrH,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;IACrB,CAAC;IACD,OAAO,IAAI,CAAC,kBAAkB,MAAM,iDAAiD,CAAC,CAAC;AACzF,CAAC"}
|
package/dist/git/index.d.ts
CHANGED
package/dist/git/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/git/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/git/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
package/dist/git/index.js
CHANGED
package/dist/git/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/git/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/git/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3C,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep both: two writers on one disk who each commit keep both commits, and
|
|
3
|
+
* so do two machines that change one repository at once.
|
|
4
|
+
*
|
|
5
|
+
* A repository's records stand in one order, each a whole state. A record
|
|
6
|
+
* that follows the state this machine last recorded or applied replaces it.
|
|
7
|
+
* One that does not was made beside it: both moved on from the state where
|
|
8
|
+
* their chains of records meet, the base, and Apply joins the two here
|
|
9
|
+
* instead of letting the later replace the earlier. The machine whose record
|
|
10
|
+
* the cloud accepted first joins: on the other, its own later record protects
|
|
11
|
+
* its ground from the first (src/apply/local-work.ts). The join stands here
|
|
12
|
+
* as a person's change to theirs, so this machine records it after theirs and
|
|
13
|
+
* the other machine applies it plainly. Nothing in a join names the machine
|
|
14
|
+
* making it, so a third machine that joins the same sides makes the same
|
|
15
|
+
* state.
|
|
16
|
+
*
|
|
17
|
+
* - A Git-directory file takes the side that changed it; theirs when both did.
|
|
18
|
+
* - A ref, loose or packed, is decided by name: the side that moved it, or
|
|
19
|
+
* the later of two on one line of history. A branch both moved apart gets
|
|
20
|
+
* a merge commit when Git merges them with no file changed differently;
|
|
21
|
+
* otherwise theirs stays and ours is kept beside it, as `git fetch` keeps
|
|
22
|
+
* what it will not overwrite, at `<name>-<commit>`; so is a ref ours made
|
|
23
|
+
* where theirs's need a folder, or the reverse. A remote-tracking ref
|
|
24
|
+
* takes theirs, which the next fetch refreshes.
|
|
25
|
+
* - A reflog both changed keeps both sides' lines, the side its ref ends at
|
|
26
|
+
* last; the stash keeps both sides' entries, ours's on top.
|
|
27
|
+
* - The index follows HEAD: the index of the side whose commit HEAD names,
|
|
28
|
+
* or ours carried onto a merge commit as `git read-tree -m` carries it.
|
|
29
|
+
*
|
|
30
|
+
* Where the merge has no answer, theirs replaces ours (`gitMergeable`): refs
|
|
31
|
+
* kept in reftable, a linked worktree, whose branches its main checkout's
|
|
32
|
+
* state holds, and sides that differ in format, home or borrowed storage.
|
|
33
|
+
* Everything Git computes is the host's, through `GitMergeGit`; the rest is
|
|
34
|
+
* decided here, so every host joins alike and the same sides always make the
|
|
35
|
+
* same state.
|
|
36
|
+
*/
|
|
37
|
+
import type { Sha256Hex } from '../entities/types.js';
|
|
38
|
+
import { type GitState } from './state.js';
|
|
39
|
+
export interface GitMergeSides {
|
|
40
|
+
/** Where the two chains of records meet. */
|
|
41
|
+
readonly base: GitState;
|
|
42
|
+
/** The state this machine last recorded or applied. */
|
|
43
|
+
readonly ours: GitState;
|
|
44
|
+
/** The state of the record being applied. */
|
|
45
|
+
readonly theirs: GitState;
|
|
46
|
+
}
|
|
47
|
+
/** Who writes the merge commits and their reflog lines. */
|
|
48
|
+
export declare const GIT_MERGE_IDENTITY: {
|
|
49
|
+
readonly name: "Amalgm";
|
|
50
|
+
readonly email: "amalgm@localhost";
|
|
51
|
+
};
|
|
52
|
+
export interface GitJoin {
|
|
53
|
+
readonly commit: string;
|
|
54
|
+
/** Its committer time in seconds, as its reflog line states it. */
|
|
55
|
+
readonly time: number;
|
|
56
|
+
}
|
|
57
|
+
/** What the merge asks Git, in the repository holding both sides' objects. */
|
|
58
|
+
export interface GitMergeGit {
|
|
59
|
+
/** Whether `ancestor` is `descendant` or reached from it. */
|
|
60
|
+
isAncestor(ancestor: string, descendant: string): Promise<boolean>;
|
|
61
|
+
/** A merge commit of `theirs` and `ours`, in that order, by
|
|
62
|
+
* GIT_MERGE_IDENTITY at the later of their committer times, when Git
|
|
63
|
+
* merges them cleanly and no file changed differently on the two sides;
|
|
64
|
+
* null otherwise.
|
|
65
|
+
* The same two commits always make the same commit. */
|
|
66
|
+
join(theirs: string, ours: string, message: string): Promise<GitJoin | null>;
|
|
67
|
+
/** What an index becomes when HEAD moves from commit `from` to `to`,
|
|
68
|
+
* keeping what it stages (`git read-tree -m`): its bytes and the meaning
|
|
69
|
+
* a GitIndex states; null when Git cannot carry it, or it skips files a
|
|
70
|
+
* sparse checkout leaves out. */
|
|
71
|
+
carryIndex(index: Uint8Array, from: string, to: string): Promise<{
|
|
72
|
+
readonly meaning: string;
|
|
73
|
+
readonly bytes: Uint8Array;
|
|
74
|
+
} | null>;
|
|
75
|
+
}
|
|
76
|
+
export interface GitMerge {
|
|
77
|
+
readonly state: GitState;
|
|
78
|
+
/** The bytes of the files the merge wrote, by blob ID. */
|
|
79
|
+
readonly made: ReadonlyMap<string, Uint8Array>;
|
|
80
|
+
/** The merge commits it made. */
|
|
81
|
+
readonly joins: readonly string[];
|
|
82
|
+
}
|
|
83
|
+
/** Whether the sides can be joined; otherwise theirs replaces ours. */
|
|
84
|
+
export declare function gitMergeable({ base, ours, theirs }: GitMergeSides): boolean;
|
|
85
|
+
/** Where two chains of records meet: the nearest version both reach through
|
|
86
|
+
* their parents, or null when they never do. The chains step back in turn,
|
|
87
|
+
* so the walk costs twice the longer way back. */
|
|
88
|
+
export declare function gitMergeBase(ours: string, theirs: string, parentOf: (version: string) => Promise<string | null>): Promise<string | null>;
|
|
89
|
+
/** The sides joined by the file rule alone, refs as files and the index
|
|
90
|
+
* theirs's: what a receiver's Git acts under while it merges, its
|
|
91
|
+
* configuration and borrowed storage. */
|
|
92
|
+
export declare function gitMergeFiles(sides: GitMergeSides): GitState;
|
|
93
|
+
/** The sides joined. `bytes` reads any blob a side's files name. */
|
|
94
|
+
export declare function gitMergeStates(sides: GitMergeSides, bytes: (oid: string) => Promise<Uint8Array>, git: GitMergeGit, sha256: Sha256Hex): Promise<GitMerge>;
|
|
95
|
+
//# sourceMappingURL=merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.d.ts","sourceRoot":"","sources":["../../src/git/merge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAOtD,OAAO,EAA2D,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGpG,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,uDAAuD;IACvD,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC;CAC3B;AAED,2DAA2D;AAC3D,eAAO,MAAM,kBAAkB;;;CAAyD,CAAC;AAEzF,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,mEAAmE;IACnE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,8EAA8E;AAC9E,MAAM,WAAW,WAAW;IAC1B,6DAA6D;IAC7D,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACnE;;;;2DAIuD;IACvD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;IAC7E;;;qCAGiC;IACjC,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;KAAE,GAAG,IAAI,CAAC,CAAC;CACnI;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/C,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,aAAa,GAAG,OAAO,CAM3E;AAED;;kDAEkD;AAClD,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAC7D,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAe/E;AA+CD;;yCAEyC;AACzC,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,QAAQ,CAI5D;AAyCD,oEAAoE;AACpE,wBAAsB,cAAc,CAAC,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,EACpG,GAAG,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC,QAAQ,CAAC,CAuLxD"}
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keep both: two writers on one disk who each commit keep both commits, and
|
|
3
|
+
* so do two machines that change one repository at once.
|
|
4
|
+
*
|
|
5
|
+
* A repository's records stand in one order, each a whole state. A record
|
|
6
|
+
* that follows the state this machine last recorded or applied replaces it.
|
|
7
|
+
* One that does not was made beside it: both moved on from the state where
|
|
8
|
+
* their chains of records meet, the base, and Apply joins the two here
|
|
9
|
+
* instead of letting the later replace the earlier. The machine whose record
|
|
10
|
+
* the cloud accepted first joins: on the other, its own later record protects
|
|
11
|
+
* its ground from the first (src/apply/local-work.ts). The join stands here
|
|
12
|
+
* as a person's change to theirs, so this machine records it after theirs and
|
|
13
|
+
* the other machine applies it plainly. Nothing in a join names the machine
|
|
14
|
+
* making it, so a third machine that joins the same sides makes the same
|
|
15
|
+
* state.
|
|
16
|
+
*
|
|
17
|
+
* - A Git-directory file takes the side that changed it; theirs when both did.
|
|
18
|
+
* - A ref, loose or packed, is decided by name: the side that moved it, or
|
|
19
|
+
* the later of two on one line of history. A branch both moved apart gets
|
|
20
|
+
* a merge commit when Git merges them with no file changed differently;
|
|
21
|
+
* otherwise theirs stays and ours is kept beside it, as `git fetch` keeps
|
|
22
|
+
* what it will not overwrite, at `<name>-<commit>`; so is a ref ours made
|
|
23
|
+
* where theirs's need a folder, or the reverse. A remote-tracking ref
|
|
24
|
+
* takes theirs, which the next fetch refreshes.
|
|
25
|
+
* - A reflog both changed keeps both sides' lines, the side its ref ends at
|
|
26
|
+
* last; the stash keeps both sides' entries, ours's on top.
|
|
27
|
+
* - The index follows HEAD: the index of the side whose commit HEAD names,
|
|
28
|
+
* or ours carried onto a merge commit as `git read-tree -m` carries it.
|
|
29
|
+
*
|
|
30
|
+
* Where the merge has no answer, theirs replaces ours (`gitMergeable`): refs
|
|
31
|
+
* kept in reftable, a linked worktree, whose branches its main checkout's
|
|
32
|
+
* state holds, and sides that differ in format, home or borrowed storage.
|
|
33
|
+
* Everything Git computes is the host's, through `GitMergeGit`; the rest is
|
|
34
|
+
* decided here, so every host joins alike and the same sides always make the
|
|
35
|
+
* same state.
|
|
36
|
+
*/
|
|
37
|
+
import { utf8Decode, utf8Encode } from '../entities/bytes.js';
|
|
38
|
+
import { GIT_ALTERNATES_PATH } from './alternates.js';
|
|
39
|
+
import { gitBlobId } from './artifact.js';
|
|
40
|
+
import { isGitLockPath } from './plan.js';
|
|
41
|
+
import { gitChainReflog, gitDropPackedRefs, gitLooseRef, gitMergeReflog, gitPackedRefs, gitReflogLine, gitReflogTop, } from './refs.js';
|
|
42
|
+
import { gitSharesObjects, isGitOid } from './state.js';
|
|
43
|
+
import { compareGitText } from './text.js';
|
|
44
|
+
/** Who writes the merge commits and their reflog lines. */
|
|
45
|
+
export const GIT_MERGE_IDENTITY = { name: 'Amalgm', email: 'amalgm@localhost' };
|
|
46
|
+
/** Whether the sides can be joined; otherwise theirs replaces ours. */
|
|
47
|
+
export function gitMergeable({ base, ours, theirs }) {
|
|
48
|
+
const borrowed = (state) => state.files.find(file => file.path === GIT_ALTERNATES_PATH)?.oid ?? null;
|
|
49
|
+
const reftable = (state) => state.files.some(file => file.path === 'reftable');
|
|
50
|
+
return base.format === ours.format && ours.format === theirs.format && !gitSharesObjects(ours)
|
|
51
|
+
&& ours.home?.entity === theirs.home?.entity && ours.home?.path === theirs.home?.path
|
|
52
|
+
&& borrowed(ours) === borrowed(theirs) && ![base, ours, theirs].some(reftable);
|
|
53
|
+
}
|
|
54
|
+
/** Where two chains of records meet: the nearest version both reach through
|
|
55
|
+
* their parents, or null when they never do. The chains step back in turn,
|
|
56
|
+
* so the walk costs twice the longer way back. */
|
|
57
|
+
export async function gitMergeBase(ours, theirs, parentOf) {
|
|
58
|
+
if (ours === theirs)
|
|
59
|
+
return ours;
|
|
60
|
+
const at = [ours, theirs];
|
|
61
|
+
const seen = [new Set([ours]), new Set([theirs])];
|
|
62
|
+
while (at[0] !== null || at[1] !== null) {
|
|
63
|
+
for (const side of [0, 1]) {
|
|
64
|
+
const from = at[side];
|
|
65
|
+
if (from === null || from === undefined)
|
|
66
|
+
continue;
|
|
67
|
+
const parent = at[side] = await parentOf(from);
|
|
68
|
+
if (parent === null)
|
|
69
|
+
continue;
|
|
70
|
+
if (seen[1 - side].has(parent))
|
|
71
|
+
return parent;
|
|
72
|
+
seen[side].add(parent);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const FOLDER = '040000';
|
|
78
|
+
const byPath = (files) => new Map(files.map(file => [file.path, file]));
|
|
79
|
+
const same = (left, right) => left === undefined ? right === undefined : right !== undefined && left.mode === right.mode && left.oid === right.oid;
|
|
80
|
+
/** The side that changed an entry from the base; theirs when both did. */
|
|
81
|
+
const changedSide = (base, ours, theirs) => same(ours, theirs) || same(theirs, base) ? ours : theirs;
|
|
82
|
+
function fileRule(base, ours, theirs) {
|
|
83
|
+
const merged = new Map();
|
|
84
|
+
for (const path of new Set([...base.keys(), ...ours.keys(), ...theirs.keys()])) {
|
|
85
|
+
const file = changedSide(base.get(path), ours.get(path), theirs.get(path));
|
|
86
|
+
if (file !== undefined)
|
|
87
|
+
merged.set(path, file);
|
|
88
|
+
}
|
|
89
|
+
return merged;
|
|
90
|
+
}
|
|
91
|
+
const parents = (path) => {
|
|
92
|
+
const found = [];
|
|
93
|
+
for (let at = path.indexOf('/'); at > 0; at = path.indexOf('/', at + 1))
|
|
94
|
+
found.push(path.slice(0, at));
|
|
95
|
+
return found;
|
|
96
|
+
};
|
|
97
|
+
/** A tree again, sorted: where an entry stands beneath a file, theirs's
|
|
98
|
+
* whole subtree there replaces both sides', and every parent folder stands. */
|
|
99
|
+
function asTree(merged, theirs) {
|
|
100
|
+
const clashes = new Set();
|
|
101
|
+
for (const path of merged.keys()) {
|
|
102
|
+
const clash = parents(path).find(parent => merged.has(parent) && merged.get(parent).mode !== FOLDER);
|
|
103
|
+
if (clash !== undefined)
|
|
104
|
+
clashes.add(clash);
|
|
105
|
+
}
|
|
106
|
+
const clashed = (path) => clashes.has(path) || parents(path).some(parent => clashes.has(parent));
|
|
107
|
+
if (clashes.size > 0) {
|
|
108
|
+
for (const path of [...merged.keys()])
|
|
109
|
+
if (clashed(path))
|
|
110
|
+
merged.delete(path);
|
|
111
|
+
for (const [path, file] of theirs)
|
|
112
|
+
if (clashed(path))
|
|
113
|
+
merged.set(path, file);
|
|
114
|
+
}
|
|
115
|
+
for (const path of [...merged.keys()]) {
|
|
116
|
+
for (const parent of parents(path))
|
|
117
|
+
if (!merged.has(parent))
|
|
118
|
+
merged.set(parent, { path: parent, mode: FOLDER, oid: null });
|
|
119
|
+
}
|
|
120
|
+
return [...merged.values()].sort((left, right) => compareGitText(left.path, right.path));
|
|
121
|
+
}
|
|
122
|
+
/** The sides joined by the file rule alone, refs as files and the index
|
|
123
|
+
* theirs's: what a receiver's Git acts under while it merges, its
|
|
124
|
+
* configuration and borrowed storage. */
|
|
125
|
+
export function gitMergeFiles(sides) {
|
|
126
|
+
const theirs = byPath(sides.theirs.files);
|
|
127
|
+
const files = asTree(fileRule(byPath(sides.base.files), byPath(sides.ours.files), theirs), theirs);
|
|
128
|
+
return { format: sides.theirs.format, home: sides.theirs.home, files, index: sides.theirs.index };
|
|
129
|
+
}
|
|
130
|
+
const SIDES = ['base', 'ours', 'theirs'];
|
|
131
|
+
const isLooseRef = (file) => file.mode !== FOLDER
|
|
132
|
+
&& (file.path === 'HEAD' || file.path.startsWith('refs/') && !isGitLockPath(file.path));
|
|
133
|
+
const isRefFile = (file) => isLooseRef(file) || file.path === 'packed-refs';
|
|
134
|
+
const isReflogPath = (path) => path.startsWith('logs/') && !isGitLockPath(path);
|
|
135
|
+
/** Where ours is kept beside a ref that ends elsewhere, a detached HEAD's
|
|
136
|
+
* as a branch: the ref and the commit; flattened beneath its namespace when
|
|
137
|
+
* the ref's own place is taken, then with the whole commit ID. They name
|
|
138
|
+
* nothing of the machine joining, so every machine that joins the same sides
|
|
139
|
+
* names it alike. */
|
|
140
|
+
const besideNames = (name, commit) => {
|
|
141
|
+
const ref = name === 'HEAD' ? 'refs/heads/detached' : name;
|
|
142
|
+
const space = /^refs\/[^/]+\//.exec(ref)?.[0] ?? 'refs/';
|
|
143
|
+
const flat = space + ref.slice(space.length).replace(/\//g, '-');
|
|
144
|
+
return [`${ref}-${commit.slice(0, 7)}`, `${flat}-${commit.slice(0, 7)}`, `${flat}-${commit}`];
|
|
145
|
+
};
|
|
146
|
+
/** Ref names as Git allows them: none stands where another needs a folder. */
|
|
147
|
+
class RefNames {
|
|
148
|
+
#names = new Set();
|
|
149
|
+
#folders = new Set();
|
|
150
|
+
constructor(names) { for (const name of names)
|
|
151
|
+
this.add(name); }
|
|
152
|
+
has(name) { return this.#names.has(name); }
|
|
153
|
+
add(name) { this.#names.add(name); for (const folder of parents(name))
|
|
154
|
+
this.#folders.add(folder); }
|
|
155
|
+
/** Whether a new name would stand beneath one, or above others. */
|
|
156
|
+
clashes(name) { return this.#folders.has(name) || parents(name).some(folder => this.#names.has(folder)); }
|
|
157
|
+
}
|
|
158
|
+
const STASH_LOG = 'logs/refs/stash';
|
|
159
|
+
/** The sides joined. `bytes` reads any blob a side's files name. */
|
|
160
|
+
export async function gitMergeStates(sides, bytes, git, sha256) {
|
|
161
|
+
const files = {
|
|
162
|
+
base: byPath(sides.base.files), ours: byPath(sides.ours.files), theirs: byPath(sides.theirs.files),
|
|
163
|
+
};
|
|
164
|
+
const entries = (path) => SIDES.map(side => files[side].get(path));
|
|
165
|
+
const made = new Map();
|
|
166
|
+
const make = (path, text) => {
|
|
167
|
+
const content = typeof text === 'string' ? utf8Encode(text) : text;
|
|
168
|
+
const oid = gitBlobId(content, sha256);
|
|
169
|
+
made.set(oid, content);
|
|
170
|
+
return { path, mode: '100644', oid };
|
|
171
|
+
};
|
|
172
|
+
const read = async (file) => file?.oid ? utf8Decode(made.get(file.oid) ?? await bytes(file.oid)) : '';
|
|
173
|
+
const packed = new Map();
|
|
174
|
+
const packedOf = (side) => {
|
|
175
|
+
if (!packed.has(side))
|
|
176
|
+
packed.set(side, read(files[side].get('packed-refs')).then(gitPackedRefs));
|
|
177
|
+
return packed.get(side);
|
|
178
|
+
};
|
|
179
|
+
const values = new Map();
|
|
180
|
+
const value = (side, name) => {
|
|
181
|
+
const key = `${side}\0${name}`;
|
|
182
|
+
if (!values.has(key)) {
|
|
183
|
+
const loose = files[side].get(name);
|
|
184
|
+
values.set(key, loose !== undefined && isLooseRef(loose) ? read(loose).then(gitLooseRef)
|
|
185
|
+
: name === 'HEAD' ? Promise.resolve(null) : packedOf(side).then(refs => refs.get(name) ?? null));
|
|
186
|
+
}
|
|
187
|
+
return values.get(key);
|
|
188
|
+
};
|
|
189
|
+
const merged = fileRule(files.base, files.ours, files.theirs);
|
|
190
|
+
const paths = new Set(SIDES.flatMap(side => [...files[side].keys()]));
|
|
191
|
+
// A reflog both sides changed keeps both sides' lines, the side its ref
|
|
192
|
+
// ends at last, so `@{0}` names where it stands; the HEAD's and the
|
|
193
|
+
// stash's whatever else either did, and one side's dropped ref keeps the
|
|
194
|
+
// other's moved one. The stash is a stack: ours's entries go on top.
|
|
195
|
+
const mergeLog = async (path, oursLast) => {
|
|
196
|
+
const [base, ours, theirs] = entries(path);
|
|
197
|
+
if (same(ours, theirs) || same(ours, base) || same(theirs, base))
|
|
198
|
+
return;
|
|
199
|
+
if ([base, ours, theirs].some(file => file?.mode === FOLDER))
|
|
200
|
+
return;
|
|
201
|
+
if (path !== 'logs/HEAD' && path !== STASH_LOG && (ours === undefined || theirs === undefined)) {
|
|
202
|
+
merged.set(path, (ours ?? theirs));
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
const [was, own, their] = [await read(base), await read(ours), await read(theirs)];
|
|
206
|
+
const text = oursLast ? gitMergeReflog(was, own, their) : gitMergeReflog(was, their, own);
|
|
207
|
+
if (text === '')
|
|
208
|
+
merged.delete(path);
|
|
209
|
+
else
|
|
210
|
+
merged.set(path, make(path, path === STASH_LOG ? gitChainReflog(text) : text));
|
|
211
|
+
};
|
|
212
|
+
await mergeLog(STASH_LOG, true);
|
|
213
|
+
// Every ref some side holds differently: the loose files that differ, and
|
|
214
|
+
// every packed ref when packed-refs does.
|
|
215
|
+
const names = new Set();
|
|
216
|
+
for (const path of paths) {
|
|
217
|
+
const [base, ours, theirs] = entries(path);
|
|
218
|
+
if (same(base, ours) && same(ours, theirs))
|
|
219
|
+
continue;
|
|
220
|
+
if (path === 'packed-refs')
|
|
221
|
+
for (const side of SIDES)
|
|
222
|
+
for (const name of (await packedOf(side)).keys())
|
|
223
|
+
names.add(name);
|
|
224
|
+
else if ([base, ours, theirs].some(file => file !== undefined && isLooseRef(file)))
|
|
225
|
+
names.add(path);
|
|
226
|
+
}
|
|
227
|
+
const finals = new Map();
|
|
228
|
+
const kept = [];
|
|
229
|
+
const joined = new Map();
|
|
230
|
+
const keepBeside = (name, commit) => kept.push({ name, commit });
|
|
231
|
+
for (const name of [...names].sort(compareGitText)) {
|
|
232
|
+
const [base, ours, theirs] = await Promise.all(SIDES.map(side => value(side, name)));
|
|
233
|
+
if (ours === theirs || ours === base)
|
|
234
|
+
continue;
|
|
235
|
+
if (theirs === base) {
|
|
236
|
+
finals.set(name, ours);
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (name === 'refs/stash') {
|
|
240
|
+
finals.set(name, gitReflogTop(await read(merged.get(STASH_LOG))));
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
if (theirs === null || ours === null) {
|
|
244
|
+
if (theirs === null)
|
|
245
|
+
finals.set(name, ours);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
if (!isGitOid(sides.theirs.format, ours) || !isGitOid(sides.theirs.format, theirs))
|
|
249
|
+
continue;
|
|
250
|
+
if (await git.isAncestor(theirs, ours)) {
|
|
251
|
+
finals.set(name, ours);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (await git.isAncestor(ours, theirs) || name.startsWith('refs/remotes/'))
|
|
255
|
+
continue;
|
|
256
|
+
const message = `Merge concurrent commits on ${name.slice('refs/heads/'.length)}`;
|
|
257
|
+
const join = name.startsWith('refs/heads/') ? await git.join(theirs, ours, message) : null;
|
|
258
|
+
if (join !== null) {
|
|
259
|
+
finals.set(name, join.commit);
|
|
260
|
+
joined.set(name, { ...join, theirs, ours, message });
|
|
261
|
+
}
|
|
262
|
+
else
|
|
263
|
+
keepBeside(name, ours);
|
|
264
|
+
}
|
|
265
|
+
// A ref ours made where theirs's need a folder, or the reverse, is kept
|
|
266
|
+
// beside: Git holds no two such names.
|
|
267
|
+
const theirsNames = [...(await packedOf('theirs')).keys(),
|
|
268
|
+
...sides.theirs.files.filter(file => isLooseRef(file) && file.path !== 'HEAD').map(file => file.path)];
|
|
269
|
+
const standing = new RefNames(theirsNames.filter(name => !finals.has(name) || finals.get(name) !== null));
|
|
270
|
+
for (const [name, target] of [...finals]) {
|
|
271
|
+
if (target === null || standing.has(name) || name === 'refs/stash')
|
|
272
|
+
continue;
|
|
273
|
+
if (standing.clashes(name)) {
|
|
274
|
+
finals.delete(name);
|
|
275
|
+
keepBeside(name, target);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// The index follows the commit HEAD names. A merge commit's carries ours's
|
|
279
|
+
// staged work; one Git cannot carry is undone, and ours kept beside.
|
|
280
|
+
const final = (name) => finals.has(name) ? Promise.resolve(finals.get(name)) : value('theirs', name);
|
|
281
|
+
const commitOf = async (head, valueOf) => head?.startsWith('ref: ') ? valueOf(head.slice('ref: '.length)) : head;
|
|
282
|
+
const oursHead = await commitOf(await value('ours', 'HEAD'), name => value('ours', name));
|
|
283
|
+
const theirsHead = await commitOf(await value('theirs', 'HEAD'), name => value('theirs', name));
|
|
284
|
+
let headCommit = await commitOf(await final('HEAD'), final);
|
|
285
|
+
let index;
|
|
286
|
+
const carried = [...joined].find(([, join]) => join.commit === headCommit);
|
|
287
|
+
if (carried !== undefined) {
|
|
288
|
+
const [name, join] = carried;
|
|
289
|
+
const own = sides.ours.index;
|
|
290
|
+
const moved = own?.files.length === 1 && oursHead !== null
|
|
291
|
+
? await git.carryIndex(await bytes(own.files[0].oid), oursHead, join.commit) : null;
|
|
292
|
+
if (moved !== null)
|
|
293
|
+
index = { meaning: moved.meaning, files: [make('index', moved.bytes)] };
|
|
294
|
+
else {
|
|
295
|
+
finals.set(name, join.theirs);
|
|
296
|
+
joined.delete(name);
|
|
297
|
+
keepBeside(name, join.ours);
|
|
298
|
+
headCommit = join.theirs;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (index === undefined) {
|
|
302
|
+
const { base, ours, theirs } = { base: sides.base.index, ours: sides.ours.index, theirs: sides.theirs.index };
|
|
303
|
+
index = headCommit === oursHead && headCommit === theirsHead
|
|
304
|
+
? theirs?.meaning === base?.meaning && ours?.meaning !== base?.meaning ? ours : theirs
|
|
305
|
+
: headCommit === oursHead ? ours : theirs;
|
|
306
|
+
}
|
|
307
|
+
// A merge commit's line follows theirs's, its first parent, as `git
|
|
308
|
+
// merge` writes it.
|
|
309
|
+
const endsOurs = (end, ours, theirs) => end === ours && end !== theirs;
|
|
310
|
+
for (const path of paths) {
|
|
311
|
+
if (!isReflogPath(path) || path === STASH_LOG)
|
|
312
|
+
continue;
|
|
313
|
+
const name = path.slice('logs/'.length);
|
|
314
|
+
await mergeLog(path, name === 'HEAD' ? endsOurs(headCommit, oursHead, theirsHead)
|
|
315
|
+
: !joined.has(name) && endsOurs(await final(name), await value('ours', name), await value('theirs', name)));
|
|
316
|
+
}
|
|
317
|
+
// Theirs's ref files, with each ref that ends elsewhere written loose or
|
|
318
|
+
// dropped, and ours kept beside.
|
|
319
|
+
for (const [path, file] of merged)
|
|
320
|
+
if (isRefFile(file))
|
|
321
|
+
merged.delete(path);
|
|
322
|
+
for (const file of sides.theirs.files)
|
|
323
|
+
if (isRefFile(file))
|
|
324
|
+
merged.set(file.path, file);
|
|
325
|
+
const dropped = new Set();
|
|
326
|
+
for (const [name, target] of finals) {
|
|
327
|
+
if (target === await value('theirs', name))
|
|
328
|
+
continue;
|
|
329
|
+
if (target !== null)
|
|
330
|
+
merged.set(name, make(name, `${target}\n`));
|
|
331
|
+
else {
|
|
332
|
+
if (merged.has(name) && isLooseRef(merged.get(name)))
|
|
333
|
+
merged.delete(name);
|
|
334
|
+
dropped.add(name);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const packedRefs = files.theirs.get('packed-refs');
|
|
338
|
+
if (dropped.size > 0 && packedRefs !== undefined) {
|
|
339
|
+
const text = await read(packedRefs), rest = gitDropPackedRefs(text, dropped);
|
|
340
|
+
if (rest !== text)
|
|
341
|
+
merged.set('packed-refs', make('packed-refs', rest));
|
|
342
|
+
}
|
|
343
|
+
for (const [name, target] of finals)
|
|
344
|
+
if (target !== null)
|
|
345
|
+
standing.add(name);
|
|
346
|
+
const beside = new Map();
|
|
347
|
+
for (const { name, commit } of kept) {
|
|
348
|
+
const names = besideNames(name, commit);
|
|
349
|
+
let side = names.at(-1);
|
|
350
|
+
for (const candidate of names) {
|
|
351
|
+
const holds = beside.get(candidate) ?? (standing.has(candidate) ? await final(candidate) : null);
|
|
352
|
+
if ((holds === null || holds === commit) && !standing.clashes(candidate)) {
|
|
353
|
+
side = candidate;
|
|
354
|
+
break;
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
standing.add(side);
|
|
358
|
+
beside.set(side, commit);
|
|
359
|
+
merged.set(side, make(side, `${commit}\n`));
|
|
360
|
+
const ours = files.ours.get(`logs/${name}`), theirs = files.theirs.get(`logs/${name}`);
|
|
361
|
+
if (ours !== undefined)
|
|
362
|
+
merged.set(`logs/${side}`, { ...ours, path: `logs/${side}` });
|
|
363
|
+
if (name === 'HEAD')
|
|
364
|
+
continue;
|
|
365
|
+
if (theirs !== undefined)
|
|
366
|
+
merged.set(`logs/${name}`, theirs);
|
|
367
|
+
else
|
|
368
|
+
merged.delete(`logs/${name}`);
|
|
369
|
+
}
|
|
370
|
+
const head = await final('HEAD');
|
|
371
|
+
for (const [name, join] of joined) {
|
|
372
|
+
const line = gitReflogLine(join.theirs, join.commit, GIT_MERGE_IDENTITY, join.time, `commit (merge): ${join.message}`);
|
|
373
|
+
for (const log of [`logs/${name}`, ...head === `ref: ${name}` ? ['logs/HEAD'] : []]) {
|
|
374
|
+
const file = merged.get(log);
|
|
375
|
+
if (file !== undefined && file.mode !== FOLDER)
|
|
376
|
+
merged.set(log, make(log, await read(file) + line));
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return {
|
|
380
|
+
state: { format: sides.theirs.format, home: sides.theirs.home, files: asTree(merged, files.theirs), index },
|
|
381
|
+
made,
|
|
382
|
+
joins: [...joined.values()].map(join => join.commit),
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
//# sourceMappingURL=merge.js.map
|