From 5b8c66b0feb63a03b46b8bfa11059c7d980e1bad Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Mon, 7 Mar 2022 01:58:23 +0200 Subject: [PATCH 01/34] editor script HELLA GOOD??? - https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks - https://git-scm.com/docs/githooks#_post_rewrite Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 07e4a2f1..1747d1bf 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -635,6 +635,13 @@ REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" cp "$REWRITTEN_LIST_FILE_PATH" "$REWRITTEN_LIST_BACKUP_FILE_PATH" + +echo "POST REWRITE SCRIPT" +echo "S1 $1" +echo "STDIN:" +cat /dev/stdin + + `; /** From 5647335a88e173ae81c972ec148f3c482e6ae99a Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Fri, 18 Mar 2022 00:42:42 +0200 Subject: [PATCH 02/34] initial experimentation with editorScript and reducePath Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 7 ++- reduce-path.js | 110 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100755 reduce-path.js diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 1747d1bf..2b622912 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -633,7 +633,7 @@ REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" #echo "REBASE_MERGE_DIR $REBASE_MERGE_DIR; STACKED_REBASE_DIR $STACKED_REBASE_DIR;" -cp "$REWRITTEN_LIST_FILE_PATH" "$REWRITTEN_LIST_BACKUP_FILE_PATH" +#cp "$REWRITTEN_LIST_FILE_PATH" "$REWRITTEN_LIST_BACKUP_FILE_PATH" echo "POST REWRITE SCRIPT" @@ -641,6 +641,11 @@ echo "S1 $1" echo "STDIN:" cat /dev/stdin +cat >> "$REWRITTEN_LIST_BACKUP_FILE_PATH" < amend) + "b10448d1db22b01c21e27043a0d7c6d7072d0adb": "987f8c8682d721e9b50c2fae2828aa28e6c0c144", + + // break + // new commit "t1" + + // amend (the new commit "t1") + "15c81ce2c8880006eb8a74ec7d71251a856eef63": "b902571d8bef8f2209880daf51730da3f02bd9b3", + + // git rebase --continue (finishes) (NOTE! oldest to newest) + "b10448d1db22b01c21e27043a0d7c6d7072d0adb": "987f8c8682d721e9b50c2fae2828aa28e6c0c144", + "8836773af698905372b8284afeed2df8f2685842": "6d3d4acce8a660e7b904b4e3b0d7d43394c20b0d", + "c43371edad9fba2d1a92f331587c297cbed8b061": "e3b3c1b8adfa4300c939624530395d553ffa911a", + "c0316d944cfe60072cce68314b6b5b98f763dcf0": "32a8c3b42bca173611582e2e464eb7abca19d76a", + "53fcfda81f332a2cc1dda029f111ef68b8d420c2": "bafdb356c77a481aaeaab0fef59189337b66c5d3", + "faa4c9eab2e88a17863bf228c7aaec2ea5b10105": "85110af252f814d3537b7f41653e3b9624799bcb", + "33ccec0597809093a747d50fd50cb2c541b8410d": "28a2cd3d5ae4407eee50d7ecbec47b0d1ad08fd4", + "9991de74cfc1e49b179dc84b6781a9f96b404f11": "fa5422455629d33442669ecaba689ad6817224df", +} + +reducePath(obj2) +console.log(obj2) + +const obj2afterGitLog = { + "fa5422455629d33442669ecaba689ad6817224df": "fa5422455629d33442669ecaba689ad6817224df", + "28a2cd3d5ae4407eee50d7ecbec47b0d1ad08fd4": "28a2cd3d5ae4407eee50d7ecbec47b0d1ad08fd4", + "85110af252f814d3537b7f41653e3b9624799bcb": "85110af252f814d3537b7f41653e3b9624799bcb", + "bafdb356c77a481aaeaab0fef59189337b66c5d3": "bafdb356c77a481aaeaab0fef59189337b66c5d3", + "b902571d8bef8f2209880daf51730da3f02bd9b3": "b902571d8bef8f2209880daf51730da3f02bd9b3", + "32a8c3b42bca173611582e2e464eb7abca19d76a": "32a8c3b42bca173611582e2e464eb7abca19d76a", + "e3b3c1b8adfa4300c939624530395d553ffa911a": "e3b3c1b8adfa4300c939624530395d553ffa911a", + "6d3d4acce8a660e7b904b4e3b0d7d43394c20b0d": "6d3d4acce8a660e7b904b4e3b0d7d43394c20b0d", + "987f8c8682d721e9b50c2fae2828aa28e6c0c144": "987f8c8682d721e9b50c2fae2828aa28e6c0c144", + "713145824cf7fd2d120b9a051cb3d518901cc951": "713145824cf7fd2d120b9a051cb3d518901cc951", + "661ff363a859694a0c7ab56198cc1812f591bdd0": "661ff363a859694a0c7ab56198cc1812f591bdd0", + "7a425ca01afc0352757e0996533e014156b2b74f": "7a425ca01afc0352757e0996533e014156b2b74f", + "e5933fe148db4502762536cb5267d6872d357636": "e5933fe148db4502762536cb5267d6872d357636", + "c145226b4dccfbd9643218b9685de0721a728a93": "c145226b4dccfbd9643218b9685de0721a728a93", + "183c58def91293dff605ed1c4a4639214e07ad0e": "183c58def91293dff605ed1c4a4639214e07ad0e", + "3ac39e60ea4a4419ea2bd45f046ae5253db28b31": "3ac39e60ea4a4419ea2bd45f046ae5253db28b31", + "6775d4aed6207b75774fecc3329c4a723c1499f0": "6775d4aed6207b75774fecc3329c4a723c1499f0", +} + +assert.deepStrictEqual(Object.values(obj2), Object.values(obj2afterGitLog)) + +// "b902571d8bef8f2209880daf51730da3f02bd9b3" From bc211d5ddf99b5b3388633447a1c986adb54c856 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Fri, 18 Mar 2022 01:01:44 +0200 Subject: [PATCH 03/34] more experimentation Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 12 ++++++------ reduce-path.js | 16 +++++++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 2b622912..4047d0bc 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -636,15 +636,15 @@ REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" #cp "$REWRITTEN_LIST_FILE_PATH" "$REWRITTEN_LIST_BACKUP_FILE_PATH" -echo "POST REWRITE SCRIPT" -echo "S1 $1" -echo "STDIN:" -cat /dev/stdin +#cat >> "$REWRITTEN_LIST_BACKUP_FILE_PATH" <> "$REWRITTEN_LIST_BACKUP_FILE_PATH" < lines.split("\n")) +// .map(lines => lines.slice(1)) // remove $1 +// .filter(lines => lines.length && lines.every(line => line.length)) // remove empty "amend" +// console.log({ lists: rewrittenLists }); + +const rewrittenLists = rewrittenList + .split("\n\n") From d19d21a86ffaa9ab2c331c0eb68cf6418cd49e32 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Fri, 18 Mar 2022 01:45:53 +0200 Subject: [PATCH 04/34] more experimentation. fix reducePath - if key === value, don't reduce itself Signed-off-by: Kipras Melnikovas --- reduce-path.js | 103 ++++++++++++++----------------------------------- 1 file changed, 30 insertions(+), 73 deletions(-) diff --git a/reduce-path.js b/reduce-path.js index 3bc2a4e7..72b327db 100755 --- a/reduce-path.js +++ b/reduce-path.js @@ -12,11 +12,16 @@ const obj1 = { "d": "e", "g": "h", + + "x": "x", + + "y": "z", + "z": "z", } reducePath(obj1) console.log(obj1) -assert.deepStrictEqual(obj1, { "a": "e", "g": "h" }) +assert.deepStrictEqual(obj1, { "a": "e", "g": "h", "x": "x", "y": "z" }) function reducePath(obj) { let prevSize = Infinity @@ -27,6 +32,12 @@ function reducePath(obj) { prevSize = entries.length for (const [key, value] of entries) { + const keyIsValue = key === value + if (keyIsValue) { + // would delete itself, thus skip + continue + } + const gotReducedAlready = !(key in obj) if (gotReducedAlready) { continue @@ -34,6 +45,7 @@ function reducePath(obj) { const valueIsAnotherKey = value in obj if (valueIsAnotherKey) { + console.log("reducing. old:", key, "->", value, ";", value, "->", obj[value], "new:", key, "->", obj[value]) // reduce obj[key] = obj[value] delete obj[value] @@ -42,83 +54,28 @@ function reducePath(obj) { } } -const obj2 = { - // original git log --pretty="format:%H" (NOTE! newest to oldest) - "9991de74cfc1e49b179dc84b6781a9f96b404f11": "9991de74cfc1e49b179dc84b6781a9f96b404f11", - "33ccec0597809093a747d50fd50cb2c541b8410d": "33ccec0597809093a747d50fd50cb2c541b8410d", - "faa4c9eab2e88a17863bf228c7aaec2ea5b10105": "faa4c9eab2e88a17863bf228c7aaec2ea5b10105", - "53fcfda81f332a2cc1dda029f111ef68b8d420c2": "53fcfda81f332a2cc1dda029f111ef68b8d420c2", - // break + commit new + amend new - "c0316d944cfe60072cce68314b6b5b98f763dcf0": "c0316d944cfe60072cce68314b6b5b98f763dcf0", - "c43371edad9fba2d1a92f331587c297cbed8b061": "c43371edad9fba2d1a92f331587c297cbed8b061", - "8836773af698905372b8284afeed2df8f2685842": "8836773af698905372b8284afeed2df8f2685842", - "b10448d1db22b01c21e27043a0d7c6d7072d0adb": "b10448d1db22b01c21e27043a0d7c6d7072d0adb", // reword - "713145824cf7fd2d120b9a051cb3d518901cc951": "713145824cf7fd2d120b9a051cb3d518901cc951", - "661ff363a859694a0c7ab56198cc1812f591bdd0": "661ff363a859694a0c7ab56198cc1812f591bdd0", - "7a425ca01afc0352757e0996533e014156b2b74f": "7a425ca01afc0352757e0996533e014156b2b74f", - "e5933fe148db4502762536cb5267d6872d357636": "e5933fe148db4502762536cb5267d6872d357636", - "c145226b4dccfbd9643218b9685de0721a728a93": "c145226b4dccfbd9643218b9685de0721a728a93", - "183c58def91293dff605ed1c4a4639214e07ad0e": "183c58def91293dff605ed1c4a4639214e07ad0e", - "3ac39e60ea4a4419ea2bd45f046ae5253db28b31": "3ac39e60ea4a4419ea2bd45f046ae5253db28b31", - "6775d4aed6207b75774fecc3329c4a723c1499f0": "6775d4aed6207b75774fecc3329c4a723c1499f0", - - - // reword ($1 => amend) - "b10448d1db22b01c21e27043a0d7c6d7072d0adb": "987f8c8682d721e9b50c2fae2828aa28e6c0c144", - - // break - // new commit "t1" - - // amend (the new commit "t1") - "15c81ce2c8880006eb8a74ec7d71251a856eef63": "b902571d8bef8f2209880daf51730da3f02bd9b3", - - // git rebase --continue (finishes) (NOTE! oldest to newest) - "b10448d1db22b01c21e27043a0d7c6d7072d0adb": "987f8c8682d721e9b50c2fae2828aa28e6c0c144", - "8836773af698905372b8284afeed2df8f2685842": "6d3d4acce8a660e7b904b4e3b0d7d43394c20b0d", - "c43371edad9fba2d1a92f331587c297cbed8b061": "e3b3c1b8adfa4300c939624530395d553ffa911a", - "c0316d944cfe60072cce68314b6b5b98f763dcf0": "32a8c3b42bca173611582e2e464eb7abca19d76a", - "53fcfda81f332a2cc1dda029f111ef68b8d420c2": "bafdb356c77a481aaeaab0fef59189337b66c5d3", - "faa4c9eab2e88a17863bf228c7aaec2ea5b10105": "85110af252f814d3537b7f41653e3b9624799bcb", - "33ccec0597809093a747d50fd50cb2c541b8410d": "28a2cd3d5ae4407eee50d7ecbec47b0d1ad08fd4", - "9991de74cfc1e49b179dc84b6781a9f96b404f11": "fa5422455629d33442669ecaba689ad6817224df", -} - -reducePath(obj2) -console.log(obj2) - -const obj2afterGitLog = { - "fa5422455629d33442669ecaba689ad6817224df": "fa5422455629d33442669ecaba689ad6817224df", - "28a2cd3d5ae4407eee50d7ecbec47b0d1ad08fd4": "28a2cd3d5ae4407eee50d7ecbec47b0d1ad08fd4", - "85110af252f814d3537b7f41653e3b9624799bcb": "85110af252f814d3537b7f41653e3b9624799bcb", - "bafdb356c77a481aaeaab0fef59189337b66c5d3": "bafdb356c77a481aaeaab0fef59189337b66c5d3", - "b902571d8bef8f2209880daf51730da3f02bd9b3": "b902571d8bef8f2209880daf51730da3f02bd9b3", - "32a8c3b42bca173611582e2e464eb7abca19d76a": "32a8c3b42bca173611582e2e464eb7abca19d76a", - "e3b3c1b8adfa4300c939624530395d553ffa911a": "e3b3c1b8adfa4300c939624530395d553ffa911a", - "6d3d4acce8a660e7b904b4e3b0d7d43394c20b0d": "6d3d4acce8a660e7b904b4e3b0d7d43394c20b0d", - "987f8c8682d721e9b50c2fae2828aa28e6c0c144": "987f8c8682d721e9b50c2fae2828aa28e6c0c144", - "713145824cf7fd2d120b9a051cb3d518901cc951": "713145824cf7fd2d120b9a051cb3d518901cc951", - "661ff363a859694a0c7ab56198cc1812f591bdd0": "661ff363a859694a0c7ab56198cc1812f591bdd0", - "7a425ca01afc0352757e0996533e014156b2b74f": "7a425ca01afc0352757e0996533e014156b2b74f", - "e5933fe148db4502762536cb5267d6872d357636": "e5933fe148db4502762536cb5267d6872d357636", - "c145226b4dccfbd9643218b9685de0721a728a93": "c145226b4dccfbd9643218b9685de0721a728a93", - "183c58def91293dff605ed1c4a4639214e07ad0e": "183c58def91293dff605ed1c4a4639214e07ad0e", - "3ac39e60ea4a4419ea2bd45f046ae5253db28b31": "3ac39e60ea4a4419ea2bd45f046ae5253db28b31", - "6775d4aed6207b75774fecc3329c4a723c1499f0": "6775d4aed6207b75774fecc3329c4a723c1499f0", -} - -// assert.deepStrictEqual(Object.values(obj2), Object.values(obj2afterGitLog)) - -// "b902571d8bef8f2209880daf51730da3f02bd9b3" - -const rewrittenList = fs.readFileSync(".git/stacked-rebase/rewritten-list", { encoding: "utf-8" }) -console.log({ rewrittenList }) +const prefix = "" // "test/.tmp-described.off/" +const rewrittenListFile = fs.readFileSync(prefix + ".git/stacked-rebase/rewritten-list", { encoding: "utf-8" }) +console.log({ rewrittenListFile }) -// const rewrittenLists = rewrittenList +// const rewrittenLists = rewrittenListFile // .split("\n---\n") // .map(lines => lines.split("\n")) // .map(lines => lines.slice(1)) // remove $1 // .filter(lines => lines.length && lines.every(line => line.length)) // remove empty "amend" // console.log({ lists: rewrittenLists }); -const rewrittenLists = rewrittenList +const rewrittenLists = rewrittenListFile .split("\n\n") + .map(lists => lists.split("\n")) + .map(list => list[list.length - 1] === "" ? list.slice(0, -1) : list) + // .slice(0, -1) + .filter(list => list.length) + .map(list => list.map(line => line.split(" "))) + .map(list => Object.fromEntries(list)) +console.log({ rewrittenLists }); + +const reducedRewrittenLists = rewrittenLists + .map(list => (reducePath(list), list)) + +console.log({ reducedRewrittenLists }); From f844c4aaad7320daa43eed4e7411d02027fa642e Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Fri, 18 Mar 2022 02:05:54 +0200 Subject: [PATCH 05/34] find the (bug?) in git's post-rewrite hook, attempt initial fix by merging `amend`s & `rebase`s, figure out why fix is not sufficient for us yet Signed-off-by: Kipras Melnikovas --- reduce-path.js | 62 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/reduce-path.js b/reduce-path.js index 72b327db..1af4e625 100755 --- a/reduce-path.js +++ b/reduce-path.js @@ -52,6 +52,15 @@ function reducePath(obj) { } } } + + /** + * we mutate the object, so NOT returning it makes it clear + * that this function causes a side-effect (mutates the original object). + * + * but, in multiple cases when mapping, we forget to return the object, + * so instead we'll do it here: + */ + return obj } const prefix = "" // "test/.tmp-described.off/" @@ -76,6 +85,57 @@ const rewrittenLists = rewrittenListFile console.log({ rewrittenLists }); const reducedRewrittenLists = rewrittenLists - .map(list => (reducePath(list), list)) + // .map(list => (reducePath(list), list)) + .map(list => reducePath(list)) console.log({ reducedRewrittenLists }); + +const last = reducedRewrittenLists[reducedRewrittenLists.length - 1] +const lastVals = Object.values(last).reverse() +console.log({ lastVals }); +/** + * compare with + * git log --pretty=format:"%H" + */ + +/** + * ayo lol, prolly found a bug in git -- the output in + * rewritten-list, or /dev/stdin, after the rebase is done, + * is not fully complete -- it doesn't correctly print the SHAs + * that got changed while the rebase was paused, + * e.g. commit --amend while paused by "edit" or "break" commands. + * + * thus, at least until we report it and confirm it's actually a bug + * and not intended this way, + * we can get the desired output for ourselves + * by merging all the rewrittenLists! + * + */ + +/** + * TODO verify keys/values are not identical in a bad way + */ +const merge = (A, B) => ({ + ...A, + ...B +}) + +const mergedReducedRewrittenLists = reducedRewrittenLists.reduce((acc, curr) => reducePath(merge(acc, curr)), {}) + +console.log({ mergedReducedRewrittenLists }); +const vals = Object.values(mergedReducedRewrittenLists).reverse() +console.log({ vals }); + +/** + * it fixes the above issues! + * + * but wait! we cannot just merge like this, + * because when we take the values, + * the commits whom were not the full list (and only 1 commit remap, because --amend, + * or not full rebase up till the initial branch (TODO need to confirm the 2nd case)), + * end up being in the wrong place (they end up either in the start or the end). + * + * the problem is that we don't know __when__ the rewrite of the commit happened. + * TODO need to track that as well? + * + */ From 34e3d42e9d9c43bb0e9cb7c45bda2805dfbfd1c7 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Fri, 18 Mar 2022 19:54:25 +0200 Subject: [PATCH 06/34] add the current commit's SHA to the rewritten list Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 4047d0bc..055119d7 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -643,6 +643,7 @@ REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" #EOF cat >> "$REWRITTEN_LIST_BACKUP_FILE_PATH" < Date: Sat, 19 Mar 2022 00:31:07 +0200 Subject: [PATCH 07/34] move deletion into a separate phase so that multiple keys w/ same value all get reduced Signed-off-by: Kipras Melnikovas --- reduce-path.js | 54 +++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/reduce-path.js b/reduce-path.js index 1af4e625..17748b0e 100755 --- a/reduce-path.js +++ b/reduce-path.js @@ -17,19 +17,51 @@ const obj1 = { "y": "z", "z": "z", + + /** + * this might mean that we need to go backwards + * rather than forwards + * (multiple commits can be reported as rewritten into one, + * but i don't think the opposite is possible) + * + * ~~and/or we might need another phase, + * because currently, A -> F, + * and both B and C stay at D.~~ + * done + * + */ + "A": "D", + "B": "D", + "C": "D", + "D": "E", + "E": "F", } reducePath(obj1) console.log(obj1) -assert.deepStrictEqual(obj1, { "a": "e", "g": "h", "x": "x", "y": "z" }) +assert.deepStrictEqual(obj1, { + "a": "e", + + "g": "h", + + "x": "x", + + "y": "z", + + "A": "F", + "B": "F", + "C": "F", +}) function reducePath(obj) { - let prevSize = Infinity + let prevSize = -Infinity let entries + let keysMarkedForDeletion = new Set() // as long as it continues to improve - while ((entries = Object.entries(obj)).length < prevSize) { - prevSize = entries.length + while (keysMarkedForDeletion.size > prevSize) { + prevSize = keysMarkedForDeletion.size + entries = Object.entries(obj) for (const [key, value] of entries) { const keyIsValue = key === value @@ -38,21 +70,25 @@ function reducePath(obj) { continue } - const gotReducedAlready = !(key in obj) - if (gotReducedAlready) { - continue - } + // const gotReducedAlready = !(key in obj) + // if (gotReducedAlready) { + // continue + // } const valueIsAnotherKey = value in obj if (valueIsAnotherKey) { console.log("reducing. old:", key, "->", value, ";", value, "->", obj[value], "new:", key, "->", obj[value]) // reduce obj[key] = obj[value] - delete obj[value] + keysMarkedForDeletion.add(value) } } } + for (const key of keysMarkedForDeletion.keys()) { + delete obj[key] + } + /** * we mutate the object, so NOT returning it makes it clear * that this function causes a side-effect (mutates the original object). From 49fa3fd08a93ff1aecd24d3065dccd9407fd2d74 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sat, 19 Mar 2022 01:48:12 +0200 Subject: [PATCH 08/34] undo the "current commit printing into rewrittenList" Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 055119d7..715e0190 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -642,8 +642,13 @@ REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" #--- #EOF +#cat >> "$REWRITTEN_LIST_BACKUP_FILE_PATH" <> "$REWRITTEN_LIST_BACKUP_FILE_PATH" < Date: Sat, 19 Mar 2022 01:51:16 +0200 Subject: [PATCH 09/34] add back the "$1" in rewrittenList (type of command - amend or rebase) Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 715e0190..edd1ead6 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -649,6 +649,7 @@ REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" #EOF cat >> "$REWRITTEN_LIST_BACKUP_FILE_PATH" < Date: Sat, 19 Mar 2022 03:14:40 +0200 Subject: [PATCH 10/34] merge amends into the 1st rebase that comes after them Signed-off-by: Kipras Melnikovas --- reduce-path.js | 135 ++++++++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/reduce-path.js b/reduce-path.js index 17748b0e..5d7a0a3c 100755 --- a/reduce-path.js +++ b/reduce-path.js @@ -4,6 +4,7 @@ const assert = require("assert") const fs = require("fs") +const { execSync } = require("child_process") const obj1 = { "a": "b", @@ -103,75 +104,93 @@ const prefix = "" // "test/.tmp-described.off/" const rewrittenListFile = fs.readFileSync(prefix + ".git/stacked-rebase/rewritten-list", { encoding: "utf-8" }) console.log({ rewrittenListFile }) -// const rewrittenLists = rewrittenListFile -// .split("\n---\n") -// .map(lines => lines.split("\n")) -// .map(lines => lines.slice(1)) // remove $1 -// .filter(lines => lines.length && lines.every(line => line.length)) // remove empty "amend" -// console.log({ lists: rewrittenLists }); +/** + * $1 (amend/rebase) + */ +const extraOperatorLineCount = 1 const rewrittenLists = rewrittenListFile .split("\n\n") .map(lists => lists.split("\n")) .map(list => list[list.length - 1] === "" ? list.slice(0, -1) : list) // .slice(0, -1) - .filter(list => list.length) - .map(list => list.map(line => line.split(" "))) - .map(list => Object.fromEntries(list)) -console.log({ rewrittenLists }); - -const reducedRewrittenLists = rewrittenLists - // .map(list => (reducePath(list), list)) - .map(list => reducePath(list)) - -console.log({ reducedRewrittenLists }); + .filter(list => list.length > extraOperatorLineCount) + .map(list => ({ + type: list[0], + mapping: Object.fromEntries( + list.slice(1).map(line => line.split(" ")) + ) + }) + ) + // .map(list => Object.fromEntries(list)) +console.log("rewrittenLists", rewrittenLists) + +let prev = [] +let mergedReducedRewrittenLists = [] +for (const list of rewrittenLists) { + if (list.type === "amend") { + prev.push(list) + } else if (list.type === "rebase") { + /** + * merging time + */ + for (const amend of prev) { + assert.equal(Object.keys(amend.mapping).length, 1) + + const [key, value] = Object.entries(amend.mapping)[0] + + /** + * (try to) merge + */ + if (key in list.mapping) { + if (value === list.mapping[key]) { + // pointless + continue + } else { + throw new Error( + `NOT IMPLEMENTED - identical key in 'amend' and 'rebase', but different values.` + + `(key = "${key}", amend's value = "${value}", rebase's value = "${list.mapping[key]}")` + ) + } + } else { + if (Object.values(list.mapping).includes(key)) { + /** + * add the single new entry of amend's mapping into rebase's mapping. + * it will get `reducePath`'d later. + */ + Object.assign(list.mapping, amend.mapping) + } else { + throw new Error( + "NOT IMPLEMENTED - neither key nor value of 'amend' was included in the 'rebase'." + + "could be that we missed the ordering, or when we call 'reducePath', or something else." + ) + } + } + } -const last = reducedRewrittenLists[reducedRewrittenLists.length - 1] -const lastVals = Object.values(last).reverse() -console.log({ lastVals }); + prev = [] + reducePath(list.mapping) + mergedReducedRewrittenLists.push(list) + } else { + throw new Error(`invalid list type (got "${list.type}")`) + } +} /** - * compare with - * git log --pretty=format:"%H" + * TODO handle multiple rebases + * or, multiple separate files for each new rebase, + * since could potentially lose some info if skipping partial steps? */ -/** - * ayo lol, prolly found a bug in git -- the output in - * rewritten-list, or /dev/stdin, after the rebase is done, - * is not fully complete -- it doesn't correctly print the SHAs - * that got changed while the rebase was paused, - * e.g. commit --amend while paused by "edit" or "break" commands. - * - * thus, at least until we report it and confirm it's actually a bug - * and not intended this way, - * we can get the desired output for ourselves - * by merging all the rewrittenLists! - * - */ +console.log("mergedReducedRewrittenLists", mergedReducedRewrittenLists) -/** - * TODO verify keys/values are not identical in a bad way - */ -const merge = (A, B) => ({ - ...A, - ...B -}) +const b4 = Object.keys(mergedReducedRewrittenLists[0].mapping) +const after = Object.values(mergedReducedRewrittenLists[0].mapping) -const mergedReducedRewrittenLists = reducedRewrittenLists.reduce((acc, curr) => reducePath(merge(acc, curr)), {}) +fs.writeFileSync("b4", b4.join("\n") + "\n") +fs.writeFileSync("after", after.join("\n") + "\n") -console.log({ mergedReducedRewrittenLists }); -const vals = Object.values(mergedReducedRewrittenLists).reverse() -console.log({ vals }); +const N = after.length +console.log({ N }) -/** - * it fixes the above issues! - * - * but wait! we cannot just merge like this, - * because when we take the values, - * the commits whom were not the full list (and only 1 commit remap, because --amend, - * or not full rebase up till the initial branch (TODO need to confirm the 2nd case)), - * end up being in the wrong place (they end up either in the start or the end). - * - * the problem is that we don't know __when__ the rewrite of the commit happened. - * TODO need to track that as well? - * - */ +execSync(`git log --pretty=format:"%H" | head -n ${N} | tac - > curr`) +execSync(`diff -us curr after`, { stdio: "inherit" }) From dbfd890f963b0b5da4e9b02e774796ef20f6d8b9 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sat, 19 Mar 2022 19:46:34 +0200 Subject: [PATCH 11/34] create the full/combined rewritten-list Signed-off-by: Kipras Melnikovas --- reduce-path.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/reduce-path.js b/reduce-path.js index 5d7a0a3c..f09d5db2 100755 --- a/reduce-path.js +++ b/reduce-path.js @@ -183,6 +183,9 @@ for (const list of rewrittenLists) { console.log("mergedReducedRewrittenLists", mergedReducedRewrittenLists) +const combinedRewrittenList = Object.entries(mergedReducedRewrittenLists[0].mapping).map(([k, v]) => k + " " + v).join("\n") + "\n" +fs.writeFileSync("rewritten-list", combinedRewrittenList) + const b4 = Object.keys(mergedReducedRewrittenLists[0].mapping) const after = Object.values(mergedReducedRewrittenLists[0].mapping) From 9af948fdbc7f22e79efc064f789c9ae95aab6885 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sat, 19 Mar 2022 20:31:57 +0200 Subject: [PATCH 12/34] turn reduce-path's logic into "combineRewrittenLists" function Signed-off-by: Kipras Melnikovas --- reduce-path.js | 177 ++++++++++++++++++++++++++----------------------- 1 file changed, 94 insertions(+), 83 deletions(-) diff --git a/reduce-path.js b/reduce-path.js index f09d5db2..1e9624f9 100755 --- a/reduce-path.js +++ b/reduce-path.js @@ -100,100 +100,111 @@ function reducePath(obj) { return obj } -const prefix = "" // "test/.tmp-described.off/" -const rewrittenListFile = fs.readFileSync(prefix + ".git/stacked-rebase/rewritten-list", { encoding: "utf-8" }) -console.log({ rewrittenListFile }) - -/** - * $1 (amend/rebase) - */ -const extraOperatorLineCount = 1 - -const rewrittenLists = rewrittenListFile - .split("\n\n") - .map(lists => lists.split("\n")) - .map(list => list[list.length - 1] === "" ? list.slice(0, -1) : list) - // .slice(0, -1) - .filter(list => list.length > extraOperatorLineCount) - .map(list => ({ - type: list[0], - mapping: Object.fromEntries( - list.slice(1).map(line => line.split(" ")) - ) - }) - ) - // .map(list => Object.fromEntries(list)) -console.log("rewrittenLists", rewrittenLists) - -let prev = [] -let mergedReducedRewrittenLists = [] -for (const list of rewrittenLists) { - if (list.type === "amend") { - prev.push(list) - } else if (list.type === "rebase") { - /** - * merging time - */ - for (const amend of prev) { - assert.equal(Object.keys(amend.mapping).length, 1) - - const [key, value] = Object.entries(amend.mapping)[0] - +function combineRewrittenLists(rewrittenListFile) { + /** + * $1 (amend/rebase) + */ + const extraOperatorLineCount = 1 + + const rewrittenLists = rewrittenListFile + .split("\n\n") + .map(lists => lists.split("\n")) + .map(list => list[list.length - 1] === "" ? list.slice(0, -1) : list) + // .slice(0, -1) + .filter(list => list.length > extraOperatorLineCount) + .map(list => ({ + type: list[0], + mapping: Object.fromEntries( + list.slice(1).map(line => line.split(" ")) + ) + }) + ) + // .map(list => Object.fromEntries(list)) + console.log("rewrittenLists", rewrittenLists) + + let prev = [] + let mergedReducedRewrittenLists = [] + for (const list of rewrittenLists) { + if (list.type === "amend") { + prev.push(list) + } else if (list.type === "rebase") { /** - * (try to) merge + * merging time */ - if (key in list.mapping) { - if (value === list.mapping[key]) { - // pointless - continue + for (const amend of prev) { + assert.equal(Object.keys(amend.mapping).length, 1) + + const [key, value] = Object.entries(amend.mapping)[0] + + /** + * (try to) merge + */ + if (key in list.mapping) { + if (value === list.mapping[key]) { + // pointless + continue + } else { + throw new Error( + `NOT IMPLEMENTED - identical key in 'amend' and 'rebase', but different values.` + + `(key = "${key}", amend's value = "${value}", rebase's value = "${list.mapping[key]}")` + ) + } } else { - throw new Error( - `NOT IMPLEMENTED - identical key in 'amend' and 'rebase', but different values.` - + `(key = "${key}", amend's value = "${value}", rebase's value = "${list.mapping[key]}")` - ) - } - } else { - if (Object.values(list.mapping).includes(key)) { - /** - * add the single new entry of amend's mapping into rebase's mapping. - * it will get `reducePath`'d later. - */ - Object.assign(list.mapping, amend.mapping) - } else { - throw new Error( - "NOT IMPLEMENTED - neither key nor value of 'amend' was included in the 'rebase'." - + "could be that we missed the ordering, or when we call 'reducePath', or something else." - ) + if (Object.values(list.mapping).includes(key)) { + /** + * add the single new entry of amend's mapping into rebase's mapping. + * it will get `reducePath`'d later. + */ + Object.assign(list.mapping, amend.mapping) + } else { + throw new Error( + "NOT IMPLEMENTED - neither key nor value of 'amend' was included in the 'rebase'." + + "could be that we missed the ordering, or when we call 'reducePath', or something else." + ) + } } } + + prev = [] + reducePath(list.mapping) + mergedReducedRewrittenLists.push(list) + } else { + throw new Error(`invalid list type (got "${list.type}")`) } + } + /** + * TODO handle multiple rebases + * or, multiple separate files for each new rebase, + * since could potentially lose some info if skipping partial steps? + */ + + console.log("mergedReducedRewrittenLists", mergedReducedRewrittenLists) - prev = [] - reducePath(list.mapping) - mergedReducedRewrittenLists.push(list) - } else { - throw new Error(`invalid list type (got "${list.type}")`) + const combinedRewrittenList = Object.entries(mergedReducedRewrittenLists[0].mapping).map(([k, v]) => k + " " + v).join("\n") + "\n" + fs.writeFileSync("rewritten-list", combinedRewrittenList) + + return { + mergedReducedRewrittenLists, + combinedRewrittenList, } } -/** - * TODO handle multiple rebases - * or, multiple separate files for each new rebase, - * since could potentially lose some info if skipping partial steps? - */ - -console.log("mergedReducedRewrittenLists", mergedReducedRewrittenLists) -const combinedRewrittenList = Object.entries(mergedReducedRewrittenLists[0].mapping).map(([k, v]) => k + " " + v).join("\n") + "\n" -fs.writeFileSync("rewritten-list", combinedRewrittenList) +if (!module.parent) { + const prefix = "" // "test/.tmp-described.off/" + const rewrittenListFile = fs.readFileSync(prefix + ".git/stacked-rebase/rewritten-list", { encoding: "utf-8" }) + console.log({ rewrittenListFile }) -const b4 = Object.keys(mergedReducedRewrittenLists[0].mapping) -const after = Object.values(mergedReducedRewrittenLists[0].mapping) + const { mergedReducedRewrittenLists } = combineRewrittenLists(rewrittenListFile) -fs.writeFileSync("b4", b4.join("\n") + "\n") -fs.writeFileSync("after", after.join("\n") + "\n") + const b4 = Object.keys(mergedReducedRewrittenLists[0].mapping) + const after = Object.values(mergedReducedRewrittenLists[0].mapping) + + fs.writeFileSync("b4", b4.join("\n") + "\n") + fs.writeFileSync("after", after.join("\n") + "\n") -const N = after.length -console.log({ N }) + const N = after.length + console.log({ N }) -execSync(`git log --pretty=format:"%H" | head -n ${N} | tac - > curr`) -execSync(`diff -us curr after`, { stdio: "inherit" }) + execSync(`git log --pretty=format:"%H" | head -n ${N} | tac - > curr`) + execSync(`diff -us curr after`, { stdio: "inherit" }) +} From b9507226a5ab8cbeed98c0e88424b35b594ce167 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sat, 19 Mar 2022 20:35:57 +0200 Subject: [PATCH 13/34] rename reduce-path.js to .ts Signed-off-by: Kipras Melnikovas --- reduce-path.js => reducePath.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename reduce-path.js => reducePath.ts (100%) diff --git a/reduce-path.js b/reducePath.ts similarity index 100% rename from reduce-path.js rename to reducePath.ts From 31dc64013b43bf95782bda50a8ef65bb607ea474 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 00:33:47 +0200 Subject: [PATCH 14/34] typescript-ify reduce-path Signed-off-by: Kipras Melnikovas --- reducePath.spec.ts | 54 ++++++++++++++++++ reducePath.ts | 136 ++++++++++++++++++++++----------------------- test/run.ts | 6 +- 3 files changed, 125 insertions(+), 71 deletions(-) create mode 100644 reducePath.spec.ts diff --git a/reducePath.spec.ts b/reducePath.spec.ts new file mode 100644 index 00000000..88d7320a --- /dev/null +++ b/reducePath.spec.ts @@ -0,0 +1,54 @@ +import assert from "assert"; + +import { reducePath } from "./reducePath"; + +export default function testcase() { + const obj1 = { + a: "b", + b: "c", + c: "d", + d: "e", + + g: "h", + + x: "x", + + y: "z", + z: "z", + + /** + * this might mean that we need to go backwards + * rather than forwards + * (multiple commits can be reported as rewritten into one, + * but i don't think the opposite is possible) + * + * ~~and/or we might need another phase, + * because currently, A -> F, + * and both B and C stay at D.~~ + * done + * + */ + A: "D", + B: "D", + C: "D", + D: "E", + E: "F", + }; + + reducePath(obj1); + console.log(obj1); + + assert.deepStrictEqual(obj1, { + a: "e", + + g: "h", + + x: "x", + + y: "z", + + A: "F", + B: "F", + C: "F", + }); +} diff --git a/reducePath.ts b/reducePath.ts index 1e9624f9..0e63afdc 100755 --- a/reducePath.ts +++ b/reducePath.ts @@ -1,63 +1,20 @@ -#!/usr/bin/env node +#!/usr/bin/env ts-node-dev /* eslint-disable */ -const assert = require("assert") -const fs = require("fs") -const { execSync } = require("child_process") +import assert from "assert" +import fs from "fs" +import { execSync } from "child_process" -const obj1 = { - "a": "b", - "b": "c", - "c": "d", - "d": "e", +export type StringFromToMap = { [key: string]: string } - "g": "h", - - "x": "x", - - "y": "z", - "z": "z", - - /** - * this might mean that we need to go backwards - * rather than forwards - * (multiple commits can be reported as rewritten into one, - * but i don't think the opposite is possible) - * - * ~~and/or we might need another phase, - * because currently, A -> F, - * and both B and C stay at D.~~ - * done - * - */ - "A": "D", - "B": "D", - "C": "D", - "D": "E", - "E": "F", -} - -reducePath(obj1) -console.log(obj1) -assert.deepStrictEqual(obj1, { - "a": "e", - - "g": "h", - - "x": "x", - - "y": "z", - - "A": "F", - "B": "F", - "C": "F", -}) - -function reducePath(obj) { - let prevSize = -Infinity - let entries - let keysMarkedForDeletion = new Set() +/** + * mutates `obj` and returns it too + */ +export function reducePath(obj: StringFromToMap): StringFromToMap { + let prevSize : number = -Infinity + let entries : [string, string][] + let keysMarkedForDeletion: Set = new Set() // as long as it continues to improve while (keysMarkedForDeletion.size > prevSize) { @@ -100,30 +57,61 @@ function reducePath(obj) { return obj } -function combineRewrittenLists(rewrittenListFile) { +export type RewrittenListBlockBase = { + mapping: StringFromToMap +} +export type RewrittenListBlockAmend = RewrittenListBlockBase & { + type: "amend" +} +export type RewrittenListBlockRebase = RewrittenListBlockBase & { + type: "rebase" +} +export type RewrittenListBlock = RewrittenListBlockAmend | RewrittenListBlockRebase + +export type CombineRewrittenListsRet = { + /** + * notice that this only includes rebases, no amends -- + * that's the whole point. + * + * further, probably only the 1st one is necessary, + * because it's likely that we'll start creating separate files for new rebases, + * or that might not be needed at all, because we might be able to + * --apply after every rebase, no matter if the user exited or not, + * thus we'd always have only 1 "rebase" block in the rewritten list. + */ + mergedReducedRewrittenLists: RewrittenListBlockRebase[], + + /** + * the git's standard represantation of the rewritten-list + * (no extras of ours) + */ + combinedRewrittenList: string, +} +export function combineRewrittenLists(rewrittenListFileContent: string): CombineRewrittenListsRet { /** * $1 (amend/rebase) */ - const extraOperatorLineCount = 1 + const extraOperatorLineCount = 1 as const - const rewrittenLists = rewrittenListFile + const rewrittenLists: RewrittenListBlock[] = rewrittenListFileContent .split("\n\n") .map(lists => lists.split("\n")) .map(list => list[list.length - 1] === "" ? list.slice(0, -1) : list) // .slice(0, -1) .filter(list => list.length > extraOperatorLineCount) - .map(list => ({ - type: list[0], - mapping: Object.fromEntries( - list.slice(1).map(line => line.split(" ")) + .map((list): RewrittenListBlock => ({ + type: list[0] as RewrittenListBlock["type"], + mapping: Object.fromEntries( + list.slice(1).map(line => line.split(" ") as [string, string]) ) }) ) // .map(list => Object.fromEntries(list)) console.log("rewrittenLists", rewrittenLists) - let prev = [] - let mergedReducedRewrittenLists = [] + let prev : RewrittenListBlockAmend[] = [] + let mergedReducedRewrittenLists: RewrittenListBlockRebase[] = [] + for (const list of rewrittenLists) { if (list.type === "amend") { prev.push(list) @@ -169,7 +157,7 @@ function combineRewrittenLists(rewrittenListFile) { reducePath(list.mapping) mergedReducedRewrittenLists.push(list) } else { - throw new Error(`invalid list type (got "${list.type}")`) + throw new Error(`invalid list type (got "${(list as any).type}")`) } } /** @@ -181,7 +169,7 @@ function combineRewrittenLists(rewrittenListFile) { console.log("mergedReducedRewrittenLists", mergedReducedRewrittenLists) const combinedRewrittenList = Object.entries(mergedReducedRewrittenLists[0].mapping).map(([k, v]) => k + " " + v).join("\n") + "\n" - fs.writeFileSync("rewritten-list", combinedRewrittenList) + // fs.writeFileSync("rewritten-list", combinedRewrittenList) return { mergedReducedRewrittenLists, @@ -198,13 +186,21 @@ if (!module.parent) { const b4 = Object.keys(mergedReducedRewrittenLists[0].mapping) const after = Object.values(mergedReducedRewrittenLists[0].mapping) + + const path = require("path") + const os = require("os") + const dir = path.join(os.tmpdir(), "gsr-reduce-path") + fs.mkdirSync(dir, { recursive: true }) - fs.writeFileSync("b4", b4.join("\n") + "\n") - fs.writeFileSync("after", after.join("\n") + "\n") + const b4path = path.join(dir, "b4") + const afterpath = path.join(dir, "after") + fs.writeFileSync(b4path , b4 .join("\n") + "\n") + fs.writeFileSync(afterpath, after.join("\n") + "\n") const N = after.length console.log({ N }) - execSync(`git log --pretty=format:"%H" | head -n ${N} | tac - > curr`) - execSync(`diff -us curr after`, { stdio: "inherit" }) + const currpath = path.join(dir, "curr") + execSync(`git log --pretty=format:"%H" | head -n ${N} | tac - > ${currpath}`) + execSync(`diff -us ${currpath} ${afterpath}`, { stdio: "inherit" }) } diff --git a/test/run.ts b/test/run.ts index 7ffd3882..7b892ea1 100644 --- a/test/run.ts +++ b/test/run.ts @@ -1,10 +1,14 @@ #!/usr/bin/env ts-node-dev import { testCase } from "./experiment.spec"; +import reducePathTC from "../reducePath.spec"; main(); function main() { - testCase() + Promise.all([ + testCase(), // + reducePathTC(), + ]) .then(() => process.stdout.write("\nsuccess\n\n")) .catch((e) => { process.stderr.write("\nfailure: " + e + "\n\n"); From 88487042354922da5f46bc3a941a40c9c727267c Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 00:33:56 +0200 Subject: [PATCH 15/34] =?UTF-8?q?use=20reduce-path=20(combineRewrittenList?= =?UTF-8?q?s)=20to=20make=20git-stacked-rebase=20work=20again!=20?= =?UTF-8?q?=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kipras Melnikovas --- parse-todo-of-stacked-rebase/parseNewGoodCommands.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts index 12d8ad8f..d851c79d 100644 --- a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts +++ b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts @@ -8,6 +8,7 @@ import Git from "nodegit"; import { array } from "nice-comment"; import { filenames } from "../filenames"; +import { combineRewrittenLists } from "../reduce-path"; import { parseTodoOfStackedRebase } from "./parseTodoOfStackedRebase"; import { GoodCommand, stackedRebaseCommands } from "./validator"; @@ -22,7 +23,8 @@ export function parseNewGoodCommands( const pathOfRewrittenList: string = path.join(repo.path(), "stacked-rebase", filenames.rewrittenList); const rewrittenList: string = fs.readFileSync(pathOfRewrittenList, { encoding: "utf-8" }); - const rewrittenListLines: string[] = rewrittenList.split("\n").filter((line) => !!line); + const { combinedRewrittenList } = combineRewrittenLists(rewrittenList); + const rewrittenListLines: string[] = combinedRewrittenList.split("\n").filter((line) => !!line); console.log({ rewrittenListLines }); From 6b9409dfa1dad5fc68d50553f9fc4cdf86efeefb Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 00:36:56 +0200 Subject: [PATCH 16/34] move rewrittenList file into the "done" state after an "apply" to avoid instructions duplicating Signed-off-by: Kipras Melnikovas --- apply.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apply.ts b/apply.ts index c37f976b..05a1b6f2 100644 --- a/apply.ts +++ b/apply.ts @@ -126,7 +126,7 @@ export const markThatApplied = (pathToStackedRebaseDirInsideDotGit: string): voi [getPaths(pathToStackedRebaseDirInsideDotGit)].map( ({ rewrittenListPath, needsToApplyPath, appliedPath }) => ( fs.existsSync(needsToApplyPath) && fs.unlinkSync(needsToApplyPath), // - fs.copyFileSync(rewrittenListPath, appliedPath), + fs.renameSync(rewrittenListPath, appliedPath), void 0 ) )[0]; From 3959f4a00dcf5d93a4cf7afc340b89deab048dce Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 00:43:26 +0200 Subject: [PATCH 17/34] fix "doesNeedToApply" - if there's no rewritten-list, then there's nothing to apply (?) Signed-off-by: Kipras Melnikovas --- apply.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/apply.ts b/apply.ts index 05a1b6f2..d1456a55 100644 --- a/apply.ts +++ b/apply.ts @@ -134,6 +134,13 @@ export const markThatApplied = (pathToStackedRebaseDirInsideDotGit: string): voi const doesNeedToApply = (pathToStackedRebaseDirInsideDotGit: string): boolean => { const { rewrittenListPath, needsToApplyPath, appliedPath } = getPaths(pathToStackedRebaseDirInsideDotGit); + if (!fs.existsSync(rewrittenListPath)) { + /** + * nothing to apply + */ + return false; + } + const needsToApplyPart1: boolean = fs.existsSync(needsToApplyPath); if (needsToApplyPart1) { return true; From bfe99389d7510f688fb438d773ed528795fadda7 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 00:55:54 +0200 Subject: [PATCH 18/34] fix the "no-longer-there" rewritten-list by reading the .applied file in "parseNewGoodCommands" Signed-off-by: Kipras Melnikovas --- parse-todo-of-stacked-rebase/parseNewGoodCommands.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts index d851c79d..cbe27fac 100644 --- a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts +++ b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts @@ -22,7 +22,17 @@ export function parseNewGoodCommands( logGoodCmds(oldGoodCommands); const pathOfRewrittenList: string = path.join(repo.path(), "stacked-rebase", filenames.rewrittenList); - const rewrittenList: string = fs.readFileSync(pathOfRewrittenList, { encoding: "utf-8" }); + const pathOfRewrittenListApplied: string = path.join(repo.path(), "stacked-rebase", filenames.applied); + let rewrittenList: string; + if (fs.existsSync(pathOfRewrittenList)) { + rewrittenList = fs.readFileSync(pathOfRewrittenList, { encoding: "utf-8" }); + } else if (fs.existsSync(pathOfRewrittenListApplied)) { + rewrittenList = fs.readFileSync(pathOfRewrittenListApplied, { encoding: "utf-8" }); + } else { + throw new Error( + `rewritten-list not found neither in ${pathOfRewrittenList}, nor in ${pathOfRewrittenListApplied}` + ); + } const { combinedRewrittenList } = combineRewrittenLists(rewrittenList); const rewrittenListLines: string[] = combinedRewrittenList.split("\n").filter((line) => !!line); From 3ec733cd369bf210e71ca87ea9f64b308468eb9a Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 01:01:11 +0200 Subject: [PATCH 19/34] rename the "applied" filename to "rewritten-list.applied" because makes more sense Signed-off-by: Kipras Melnikovas --- filenames.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/filenames.ts b/filenames.ts index 518504d4..8ac1f7ea 100644 --- a/filenames.ts +++ b/filenames.ts @@ -5,7 +5,7 @@ export const filenames = { rewrittenList: "rewritten-list", willNeedToApply: "will-need-to-apply", needsToApply: "needs-to-apply", - applied: "applied", + applied: "rewritten-list.applied", // gitRebaseTodo: "git-rebase-todo", // From 10c9be6aa374b8cf2a93dbc3b55a7320ad7234e1 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 01:25:08 +0200 Subject: [PATCH 20/34] refactor: extract rewritten-list reading logic into apply.ts ...because completely related to the logic of "doesNeedToApply", "markThatNeedsToApply", "markThatApplied", etc. thought about extracting the above utils into a separate, "rewrittenList.ts" file, but it doesn't make sense to separate the utils out from "apply" itself, because if we have the "rewrittenList.ts" file, then it'd sound reasonable to move the "reduce-path"'s "combineRewrittenLists" there as well, but it doesnt, because the apply logic is not related to the parsing/combinning logic. Signed-off-by: Kipras Melnikovas --- apply.ts | 42 +++++++++++++++++++ .../parseNewGoodCommands.ts | 24 ++--------- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/apply.ts b/apply.ts index d1456a55..fa4155db 100644 --- a/apply.ts +++ b/apply.ts @@ -8,6 +8,7 @@ import { noop } from "./util/noop"; import { filenames } from "./filenames"; import { configKeys } from "./configKeys"; +// eslint-disable-next-line import/no-cycle import { BranchSequencerBase, // branchSequencer, @@ -15,6 +16,7 @@ import { CallbackAfterDone, BranchSequencerArgsBase, } from "./branchSequencer"; +import { combineRewrittenLists } from "./reducePath"; export const apply: BranchSequencerBase = (args) => branchSequencer({ @@ -156,6 +158,46 @@ const doesNeedToApply = (pathToStackedRebaseDirInsideDotGit: string): boolean => return needsToApplyPart2; }; +export function readRewrittenListNotAppliedOrAppliedOrError( + repoPath: string +): { + pathOfRewrittenList: string; + pathOfRewrittenListApplied: string; + rewrittenListRaw: string; + /** + * you probably want these: + */ + combinedRewrittenList: string; + combinedRewrittenListLines: string[]; +} { + const pathOfRewrittenList: string = path.join(repoPath, "stacked-rebase", filenames.rewrittenList); + const pathOfRewrittenListApplied: string = path.join(repoPath, "stacked-rebase", filenames.applied); + + /** + * not combined yet + */ + let rewrittenListRaw: string; + if (fs.existsSync(pathOfRewrittenList)) { + rewrittenListRaw = fs.readFileSync(pathOfRewrittenList, { encoding: "utf-8" }); + } else if (fs.existsSync(pathOfRewrittenListApplied)) { + rewrittenListRaw = fs.readFileSync(pathOfRewrittenListApplied, { encoding: "utf-8" }); + } else { + throw new Error( + `rewritten-list not found neither in ${pathOfRewrittenList}, nor in ${pathOfRewrittenListApplied}` + ); + } + + const { combinedRewrittenList } = combineRewrittenLists(rewrittenListRaw); + + return { + pathOfRewrittenList, + pathOfRewrittenListApplied, + rewrittenListRaw, + combinedRewrittenList, + combinedRewrittenListLines: combinedRewrittenList.split("\n").filter((line) => !!line), + }; +} + export async function applyIfNeedsToApply({ repo, pathToStackedRebaseTodoFile, diff --git a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts index cbe27fac..1bf48dd5 100644 --- a/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts +++ b/parse-todo-of-stacked-rebase/parseNewGoodCommands.ts @@ -1,14 +1,13 @@ /* eslint-disable indent */ -import fs from "fs"; -import path from "path"; import assert from "assert"; import Git from "nodegit"; import { array } from "nice-comment"; import { filenames } from "../filenames"; -import { combineRewrittenLists } from "../reduce-path"; +// eslint-disable-next-line import/no-cycle +import { readRewrittenListNotAppliedOrAppliedOrError } from "../apply"; import { parseTodoOfStackedRebase } from "./parseTodoOfStackedRebase"; import { GoodCommand, stackedRebaseCommands } from "./validator"; @@ -21,29 +20,14 @@ export function parseNewGoodCommands( logGoodCmds(oldGoodCommands); - const pathOfRewrittenList: string = path.join(repo.path(), "stacked-rebase", filenames.rewrittenList); - const pathOfRewrittenListApplied: string = path.join(repo.path(), "stacked-rebase", filenames.applied); - let rewrittenList: string; - if (fs.existsSync(pathOfRewrittenList)) { - rewrittenList = fs.readFileSync(pathOfRewrittenList, { encoding: "utf-8" }); - } else if (fs.existsSync(pathOfRewrittenListApplied)) { - rewrittenList = fs.readFileSync(pathOfRewrittenListApplied, { encoding: "utf-8" }); - } else { - throw new Error( - `rewritten-list not found neither in ${pathOfRewrittenList}, nor in ${pathOfRewrittenListApplied}` - ); - } - const { combinedRewrittenList } = combineRewrittenLists(rewrittenList); - const rewrittenListLines: string[] = combinedRewrittenList.split("\n").filter((line) => !!line); - - console.log({ rewrittenListLines }); + const { combinedRewrittenListLines } = readRewrittenListNotAppliedOrAppliedOrError(repo.path()); const newCommits: { newSHA: string; oldSHAs: string[] }[] = []; type OldCommit = { oldSHA: string; newSHA: string; changed: boolean }; const oldCommits: OldCommit[] = []; - rewrittenListLines.map((line) => { + combinedRewrittenListLines.map((line) => { const fromToSHA = line.split(" "); assert( fromToSHA.length === 2, From fabc87e7ab5a108f7635181d2889d1786f6f48e7 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 01:34:59 +0200 Subject: [PATCH 21/34] refactor: move `applyIfNeedsToApply` up into a more related context Signed-off-by: Kipras Melnikovas --- apply.ts | 125 +++++++++++++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 63 deletions(-) diff --git a/apply.ts b/apply.ts index fa4155db..d31a75e2 100644 --- a/apply.ts +++ b/apply.ts @@ -101,6 +101,68 @@ export type ReturnOfApplyIfNeedsToApply = { userAllowedToApplyAndWeApplied: true; } ); +export async function applyIfNeedsToApply({ + repo, + pathToStackedRebaseTodoFile, + pathToStackedRebaseDirInsideDotGit, // + autoApplyIfNeeded, + config, + ...rest +}: BranchSequencerArgsBase & { + autoApplyIfNeeded: boolean; // + config: Git.Config; +}): Promise { + const needsToApply: boolean = doesNeedToApply(pathToStackedRebaseDirInsideDotGit); + const _markThatNeedsToApply = (): void => markThatNeedsToApply(pathToStackedRebaseDirInsideDotGit); + + if (!needsToApply) { + return { + neededToApply: false, + markThatNeedsToApply: _markThatNeedsToApply, + }; + } + + if (needsToApply) { + if (!autoApplyIfNeeded) { + const question = createQuestion(); + + const answerRaw: string = await question("\nneed to --apply before continuing. proceed? [Y/n/(a)lways] "); + console.log({ answerRaw }); + + const answer: string = answerRaw.trim().toLowerCase(); + + const userAllowedToApply: boolean = ["y", "yes", ""].includes(answer); + console.log({ userAllowedToApply }); + + const userAllowedToApplyAlways: boolean = ["a", "always"].includes(answer); + + if (!userAllowedToApply && !userAllowedToApplyAlways) { + return { + neededToApply: true, + userAllowedToApplyAndWeApplied: false, + markThatNeedsToApply: _markThatNeedsToApply, + }; + } + + if (userAllowedToApplyAlways) { + await config.setBool(configKeys.autoApplyIfNeeded, 1); + } + } + + await apply({ + repo, + pathToStackedRebaseTodoFile, + pathToStackedRebaseDirInsideDotGit, // + ...rest, + }); + } + + return { + neededToApply: true, + userAllowedToApplyAndWeApplied: true, // + markThatNeedsToApply: _markThatNeedsToApply, + }; +} const getPaths = ( pathToStackedRebaseDirInsideDotGit: string // @@ -197,66 +259,3 @@ export function readRewrittenListNotAppliedOrAppliedOrError( combinedRewrittenListLines: combinedRewrittenList.split("\n").filter((line) => !!line), }; } - -export async function applyIfNeedsToApply({ - repo, - pathToStackedRebaseTodoFile, - pathToStackedRebaseDirInsideDotGit, // - autoApplyIfNeeded, - config, - ...rest -}: BranchSequencerArgsBase & { - autoApplyIfNeeded: boolean; // - config: Git.Config; -}): Promise { - const needsToApply: boolean = doesNeedToApply(pathToStackedRebaseDirInsideDotGit); - const _markThatNeedsToApply = (): void => markThatNeedsToApply(pathToStackedRebaseDirInsideDotGit); - - if (!needsToApply) { - return { - neededToApply: false, - markThatNeedsToApply: _markThatNeedsToApply, - }; - } - - if (needsToApply) { - if (!autoApplyIfNeeded) { - const question = createQuestion(); - - const answerRaw: string = await question("\nneed to --apply before continuing. proceed? [Y/n/(a)lways] "); - console.log({ answerRaw }); - - const answer: string = answerRaw.trim().toLowerCase(); - - const userAllowedToApply: boolean = ["y", "yes", ""].includes(answer); - console.log({ userAllowedToApply }); - - const userAllowedToApplyAlways: boolean = ["a", "always"].includes(answer); - - if (!userAllowedToApply && !userAllowedToApplyAlways) { - return { - neededToApply: true, - userAllowedToApplyAndWeApplied: false, - markThatNeedsToApply: _markThatNeedsToApply, - }; - } - - if (userAllowedToApplyAlways) { - await config.setBool(configKeys.autoApplyIfNeeded, 1); - } - } - - await apply({ - repo, - pathToStackedRebaseTodoFile, - pathToStackedRebaseDirInsideDotGit, // - ...rest, - }); - } - - return { - neededToApply: true, - userAllowedToApplyAndWeApplied: true, // - markThatNeedsToApply: _markThatNeedsToApply, - }; -} From 6fc4fdd0b78080adec2391ae4ae4ff8878a3e031 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 01:45:29 +0200 Subject: [PATCH 22/34] fix: only move rewrittenList to applied if it exists Signed-off-by: Kipras Melnikovas --- apply.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/apply.ts b/apply.ts index d31a75e2..f48ccd81 100644 --- a/apply.ts +++ b/apply.ts @@ -190,7 +190,22 @@ export const markThatApplied = (pathToStackedRebaseDirInsideDotGit: string): voi [getPaths(pathToStackedRebaseDirInsideDotGit)].map( ({ rewrittenListPath, needsToApplyPath, appliedPath }) => ( fs.existsSync(needsToApplyPath) && fs.unlinkSync(needsToApplyPath), // - fs.renameSync(rewrittenListPath, appliedPath), + /** + * need to check if the `rewrittenListPath` exists, + * because even if it does not, then the "apply" can still go through + * and "apply", by using the already .applied file, i.e. do nothing. + * + * TODO just do not run "apply" if the file doesn't exist? + * or is there a case where it's useful still? + * + */ + fs.existsSync(rewrittenListPath) && fs.renameSync(rewrittenListPath, appliedPath), + // fs.existsSync(rewrittenListPath) + // ? fs.renameSync(rewrittenListPath, appliedPath) + // : !fs.existsSync(appliedPath) && + // (() => { + // throw new Error("applying uselessly"); + // })(), void 0 ) )[0]; From 904b8ab250e5294efc12a4329c1bc46fb5bbefe8 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 02:02:28 +0200 Subject: [PATCH 23/34] remove .git/stacked-rebase dir from existance after apply'ing (need to make work for push & others) Signed-off-by: Kipras Melnikovas --- apply.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/apply.ts b/apply.ts index f48ccd81..e01e8cc7 100644 --- a/apply.ts +++ b/apply.ts @@ -171,6 +171,7 @@ const getPaths = ( rewrittenListPath: path.join(pathToStackedRebaseDirInsideDotGit, filenames.rewrittenList), needsToApplyPath: path.join(pathToStackedRebaseDirInsideDotGit, filenames.needsToApply), appliedPath: path.join(pathToStackedRebaseDirInsideDotGit, filenames.applied), + gitRebaseTodoPath: path.join(pathToStackedRebaseDirInsideDotGit, filenames.gitRebaseTodo), } as const); export const markThatNeedsToApply = ( @@ -188,7 +189,7 @@ export const markThatNeedsToApply = ( export const markThatApplied = (pathToStackedRebaseDirInsideDotGit: string): void => [getPaths(pathToStackedRebaseDirInsideDotGit)].map( - ({ rewrittenListPath, needsToApplyPath, appliedPath }) => ( + ({ rewrittenListPath, needsToApplyPath, gitRebaseTodoPath }) => ( fs.existsSync(needsToApplyPath) && fs.unlinkSync(needsToApplyPath), // /** * need to check if the `rewrittenListPath` exists, @@ -199,13 +200,17 @@ export const markThatApplied = (pathToStackedRebaseDirInsideDotGit: string): voi * or is there a case where it's useful still? * */ - fs.existsSync(rewrittenListPath) && fs.renameSync(rewrittenListPath, appliedPath), - // fs.existsSync(rewrittenListPath) - // ? fs.renameSync(rewrittenListPath, appliedPath) - // : !fs.existsSync(appliedPath) && - // (() => { - // throw new Error("applying uselessly"); - // })(), + // fs.existsSync(rewrittenListPath) && fs.renameSync(rewrittenListPath, appliedPath), + // // fs.existsSync(rewrittenListPath) + // // ? fs.renameSync(rewrittenListPath, appliedPath) + // // : !fs.existsSync(appliedPath) && + // // (() => { + // // throw new Error("applying uselessly"); + // // })(), + fs.existsSync(rewrittenListPath) && fs.unlinkSync(rewrittenListPath), + fs.existsSync(gitRebaseTodoPath) && fs.unlinkSync(gitRebaseTodoPath), + fs.readdirSync(pathToStackedRebaseDirInsideDotGit).length === 0 && + fs.rmdirSync(pathToStackedRebaseDirInsideDotGit, { recursive: true }), void 0 ) )[0]; From e35f1d5281b3222d62f16518b93eab4864b83541 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 03:52:09 +0200 Subject: [PATCH 24/34] mkdir -p "$STACKED_REBASE_DIR" in post-rewrite script Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index edd1ead6..593e12b4 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -631,6 +631,8 @@ REWRITTEN_LIST_FILE_PATH="$REBASE_MERGE_DIR/${filenames.rewrittenList}" STACKED_REBASE_DIR="$(pwd)/.git/stacked-rebase" REWRITTEN_LIST_BACKUP_FILE_PATH="$STACKED_REBASE_DIR/${filenames.rewrittenList}" +mkdir -p "$STACKED_REBASE_DIR" + #echo "REBASE_MERGE_DIR $REBASE_MERGE_DIR; STACKED_REBASE_DIR $STACKED_REBASE_DIR;" #cp "$REWRITTEN_LIST_FILE_PATH" "$REWRITTEN_LIST_BACKUP_FILE_PATH" From 41e697114225f2ed42f136eab75264481f170803 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 02:21:17 +0200 Subject: [PATCH 25/34] explain where i'm going with this Signed-off-by: Kipras Melnikovas --- branchSequencer.ts | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/branchSequencer.ts b/branchSequencer.ts index 799d8908..01679e77 100644 --- a/branchSequencer.ts +++ b/branchSequencer.ts @@ -69,10 +69,57 @@ export const branchSequencer: BranchSequencer = async ({ callbackAfterDone = (): void => {}, gitCmd, }) => { + /** + * TODO REMOVE / modify this logic (see next comment) + */ if (!fs.existsSync(pathToStackedRebaseDirInsideDotGit)) { throw new Termination(`\n\nno stacked-rebase in progress? (nothing to ${rootLevelCommandName})\n\n`); } + // const hasPostRewriteHookInstalledWithLatestVersion = false; + /** + * + * this is only needed to get the branch names. + * + * we should instead have this as a function in the options, + * we should provide the default value, + * but allow the higher level command to overwrite it. + * + * use case differences: + * + * a) apply: + * + * needs (always or no?) to parse the new good commands + * + * b) push: + * + * since is only allowed after apply has been done, + * it doesn't actually care nor need to parse the new good commands, + * and instead can be done by simply going thru the branches + * that you would normally do with `getWantedCommitsWithBranchBoundaries`. + * + * and so it can be used even if the user has never previously used stacked rebase! + * all is needed is the `initialBranch` and the current commit, + * so that we find all the previous branches up until `initialBranch` + * and just push them! + * + * and this is safe because again, if there's something that needs to be applied, + * then before you can push, you'll need to apply first. + * + * otherwise, you can push w/o any need of apply, + * or setting up the intial rebase todo, or whatever else, + * because it's not needed! + * + * --- + * + * this is also good because we become less stateful / need less state + * to function properly. + * + * it very well could get rid of some bugs / impossible states + * that we'd sometimes end up in. + * (and no longer need to manually rm -rf .git/stacked-rebase either) + * + */ const stackedRebaseCommandsNew: GoodCommand[] = parseNewGoodCommands(repo, pathToStackedRebaseTodoFile); // const remotes: Git.Remote[] = await repo.getRemotes(); From e68cf6f5008ba3f76c6dd1f2484c5572e466767d Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 03:42:40 +0200 Subject: [PATCH 26/34] strongly refactor branchSequencer to work with simple boundaries (branch & commit) Signed-off-by: Kipras Melnikovas --- apply.ts | 35 +--- branchSequencer.ts | 195 ++++++++++------------ parse-todo-of-stacked-rebase/validator.ts | 24 +-- 3 files changed, 98 insertions(+), 156 deletions(-) diff --git a/apply.ts b/apply.ts index e01e8cc7..18c35261 100644 --- a/apply.ts +++ b/apply.ts @@ -4,7 +4,6 @@ import path from "path"; import Git from "nodegit"; import { createQuestion } from "./util/createQuestion"; -import { noop } from "./util/noop"; import { filenames } from "./filenames"; import { configKeys } from "./configKeys"; @@ -13,7 +12,6 @@ import { BranchSequencerBase, // branchSequencer, ActionInsideEachCheckedOutBranch, - CallbackAfterDone, BranchSequencerArgsBase, } from "./branchSequencer"; import { combineRewrittenLists } from "./reducePath"; @@ -32,13 +30,12 @@ const defaultApplyAction: ActionInsideEachCheckedOutBranch = async ({ repo, // // targetBranch, targetCommitSHA, - cmd, isFinalCheckout, // execSyncInRepo, }) => { const commit: Git.Commit = await Git.Commit.lookup(repo, targetCommitSHA); - console.log("will reset because", cmd.commandOrAliasName, "to commit", commit.summary(), commit.sha()); + console.log("will reset to commit", commit.sha(), "(" + commit.summary() + ")"); console.log({ isFinalCheckout }); @@ -54,36 +51,6 @@ const defaultApplyAction: ActionInsideEachCheckedOutBranch = async ({ export const getBackupPathOfPreviousStackedRebase = (pathToStackedRebaseDirInsideDotGit: string): string => pathToStackedRebaseDirInsideDotGit + ".previous"; -/** - * disabled because `forcePush` also became a thing - * and it's no longer clear what marks a stacked-rebase "done", - * - * thus making it hard to work with the temporary/previous directories - * without introducing a good amount of bugs. - * - */ -const defaultApplyCallback__disabled: CallbackAfterDone = ({ - pathToStackedRebaseDirInsideDotGit, // -}): void => { - const backupPath: string = getBackupPathOfPreviousStackedRebase(pathToStackedRebaseDirInsideDotGit); - - /** - * backup dir just in case, but in inactive path - * (so e.g --apply won't go off again accidently) - */ - if (fs.existsSync(backupPath)) { - fs.rmdirSync(backupPath, { recursive: true }); - } - fs.renameSync(pathToStackedRebaseDirInsideDotGit, backupPath); - - // diffCommands.forEach((cmd) => { - // console.log({ cmd }); - // execSyncInRepo(cmd, { ...pipestdio(repo.workdir()) }); - // }); - // -}; -noop(defaultApplyCallback__disabled); - export type ReturnOfApplyIfNeedsToApply = { markThatNeedsToApply: () => void; } & ( diff --git a/branchSequencer.ts b/branchSequencer.ts index 01679e77..dba8ca63 100644 --- a/branchSequencer.ts +++ b/branchSequencer.ts @@ -7,67 +7,26 @@ import { createExecSyncInRepo } from "./util/execSyncInRepo"; import { Termination } from "./util/error"; import { parseNewGoodCommands } from "./parse-todo-of-stacked-rebase/parseNewGoodCommands"; -import { GoodCommand } from "./parse-todo-of-stacked-rebase/validator"; +import { GoodCommand, GoodCommandStacked } from "./parse-todo-of-stacked-rebase/validator"; -export type ActionInsideEachCheckedOutBranch = (ctx: ArgsForActionInsideEachCheckedOutBranch) => void | Promise; - -/** - * - */ - -export type ArgsForActionInsideEachCheckedOutBranch = { - repo: Git.Repository; // - targetBranch: string; - targetCommitSHA: string; - cmd: GoodCommand; - isFinalCheckout: boolean; - execSyncInRepo: ReturnType; -}; - -/** - * - */ - -export type CtxForCallbackAfterDone = { +export type GetBranchesCtx = { pathToStackedRebaseDirInsideDotGit: string; -}; - -export type CallbackAfterDone = (ctx: CtxForCallbackAfterDone) => void | Promise; - -/** - * - */ - -export type BranchSequencerArgsBase = { - pathToStackedRebaseDirInsideDotGit: string; // - // goodCommands: GoodCommand[]; - pathToStackedRebaseTodoFile: string; - repo: Git.Repository; rootLevelCommandName: string; - gitCmd: string; + repo: Git.Repository; + pathToStackedRebaseTodoFile: string; }; - -export type BranchSequencerArgs = BranchSequencerArgsBase & { - // callbackBeforeBegin?: CallbackAfterDone; // TODO - actionInsideEachCheckedOutBranch: ActionInsideEachCheckedOutBranch; - delayMsBetweenCheckouts?: number; - callbackAfterDone?: CallbackAfterDone; +export type SimpleBranchAndCommit = { + commitSHA: string | null; + branchEndFullName: string; + // branchExistsYet: boolean; // TODO }; +export type GetBoundariesInclInitial = (ctx: GetBranchesCtx) => SimpleBranchAndCommit[]; -export type BranchSequencerBase = (args: BranchSequencerArgsBase) => Promise; -export type BranchSequencer = (args: BranchSequencerArgs) => Promise; - -export const branchSequencer: BranchSequencer = async ({ +const defautlGetBoundariesInclInitial: GetBoundariesInclInitial = ({ pathToStackedRebaseDirInsideDotGit, // - // goodCommands, - pathToStackedRebaseTodoFile, - repo, rootLevelCommandName, - delayMsBetweenCheckouts = 0, - // callbackBeforeBegin, - actionInsideEachCheckedOutBranch, - callbackAfterDone = (): void => {}, - gitCmd, + repo, + pathToStackedRebaseTodoFile, }) => { /** * TODO REMOVE / modify this logic (see next comment) @@ -122,74 +81,102 @@ export const branchSequencer: BranchSequencer = async ({ */ const stackedRebaseCommandsNew: GoodCommand[] = parseNewGoodCommands(repo, pathToStackedRebaseTodoFile); - // const remotes: Git.Remote[] = await repo.getRemotes(); - // const remote: Git.Remote | undefined = remotes.find((r) => - // stackedRebaseCommandsOld.find((cmd) => cmd.targets && cmd.targets[0].includes(r.name())) - // ); + for (const cmd of stackedRebaseCommandsNew) { + assert(cmd.rebaseKind === "stacked"); + assert(cmd.targets?.length); + } - // const diffCommands: string[] = stackedRebaseCommandsOld - // .map((cmd, idx) => { - // const otherCmd: GoodCommand = stackedRebaseCommandsNew[idx]; - // assert(cmd.commandName === otherCmd.commandName); - // assert(cmd.targets?.length); - // assert(otherCmd.targets?.length); - // assert(cmd.targets.every((t) => otherCmd.targets?.every((otherT) => t === otherT))); + return (stackedRebaseCommandsNew // + .filter((cmd) => cmd.rebaseKind === "stacked") as GoodCommandStacked[]) // + .map( + (cmd): SimpleBranchAndCommit => ({ + commitSHA: cmd.commitSHAThatBranchPointsTo, + branchEndFullName: cmd.targets![0], + }) + ); +}; - // const trim = (str: string): string => str.replace("refs/heads/", "").replace("refs/remotes/", ""); +/** + * + */ - // return !remote || idx === 0 // || idx === stackedRebaseCommandsOld.length - 1 - // ? "" - // : `git -c core.pager='' diff -u ${remote.name()}/${trim(cmd.targets[0])} ${trim( - // otherCmd.targets[0] - // )}`; - // }) - // .filter((cmd) => !!cmd); +export type ActionInsideEachCheckedOutBranchCtx = { + repo: Git.Repository; // + targetBranch: string; + targetCommitSHA: string; + isFinalCheckout: boolean; + execSyncInRepo: ReturnType; +}; +export type ActionInsideEachCheckedOutBranch = (ctx: ActionInsideEachCheckedOutBranchCtx) => void | Promise; +export type BranchSequencerArgsBase = { + pathToStackedRebaseDirInsideDotGit: string; // + // goodCommands: GoodCommand[]; + pathToStackedRebaseTodoFile: string; + repo: Git.Repository; + rootLevelCommandName: string; + gitCmd: string; +}; +export type BranchSequencerArgs = BranchSequencerArgsBase & { + // callbackBeforeBegin?: CallbackAfterDone; // TODO + actionInsideEachCheckedOutBranch: ActionInsideEachCheckedOutBranch; + delayMsBetweenCheckouts?: number; /** - * first actually reset, only then diff + * */ + getBoundariesInclInitial?: GetBoundariesInclInitial; +}; - // const commitsWithBranchBoundaries: CommitAndBranchBoundary[] = ( - // await getWantedCommitsWithBranchBoundaries( - // repo, // - // initialBranch - // ) - // ).reverse(); - - // const previousTargetBranchName: string = stackedRebaseCommandsNew[0] - // ? stackedRebaseCommandsNew[0].targets?.[0] ?? "" - // : ""; +export type BranchSequencerBase = (args: BranchSequencerArgsBase) => Promise; +export type BranchSequencer = (args: BranchSequencerArgs) => Promise; +export const branchSequencer: BranchSequencer = async ({ + pathToStackedRebaseDirInsideDotGit, // + pathToStackedRebaseTodoFile, + repo, + rootLevelCommandName, + delayMsBetweenCheckouts = 0, + // callbackBeforeBegin, + actionInsideEachCheckedOutBranch, + gitCmd, + // + getBoundariesInclInitial = defautlGetBoundariesInclInitial, +}) => { const execSyncInRepo = createExecSyncInRepo(repo); - const checkout = async (cmds: GoodCommand[]): Promise => { - if (!cmds.length) { + const branchesAndCommits: SimpleBranchAndCommit[] = getBoundariesInclInitial({ + pathToStackedRebaseDirInsideDotGit, + pathToStackedRebaseTodoFile, + repo, + rootLevelCommandName, + }); + + return checkout(branchesAndCommits.slice(1) as any); // TODO TS + + async function checkout(boundaries: SimpleBranchAndCommit[]): Promise { + if (!boundaries.length) { return; } - console.log("\ncheckout", cmds.length); + console.log("\ncheckout", boundaries.length); const goNext = () => new Promise((r) => { setTimeout(() => { - checkout(cmds.slice(1)).then(() => r()); + checkout(boundaries.slice(1)).then(() => r()); }, delayMsBetweenCheckouts); }); - const cmd = cmds[0]; - - assert(cmd.rebaseKind === "stacked"); - - const targetCommitSHA: string | null = cmd.commitSHAThatBranchPointsTo; + const boundary = boundaries[0]; + const branch = boundary.branchEndFullName; + const targetCommitSHA: string | null = boundary.commitSHA; if (!targetCommitSHA) { return goNext(); } - assert(cmd.targets?.length); - - let targetBranch = cmd.targets[0].replace("refs/heads/", ""); - assert(targetBranch && typeof targetBranch === "string"); + let targetBranch = branch.replace("refs/heads/", ""); + assert(targetBranch); /** * if we only have the remote branch, but it's not checked out locally, @@ -246,7 +233,7 @@ export const branchSequencer: BranchSequencer = async ({ /** * meaning we're on the latest branch */ - const isFinalCheckout: boolean = cmds.length === 1; + const isFinalCheckout: boolean = boundaries.length === 1; /** * https://libgit2.org/libgit2/#HEAD/group/checkout/git_checkout_head @@ -258,22 +245,10 @@ export const branchSequencer: BranchSequencer = async ({ repo, // targetBranch, targetCommitSHA, - cmd, isFinalCheckout, execSyncInRepo, }); return goNext(); - - // for (const cmd of stackedRebaseCommandsNew) { - // }; - }; - - await checkout(stackedRebaseCommandsNew.slice(1) as any); // TODO TS - - await callbackAfterDone({ - pathToStackedRebaseDirInsideDotGit, - }); - - return; + } }; diff --git a/parse-todo-of-stacked-rebase/validator.ts b/parse-todo-of-stacked-rebase/validator.ts index 26b903f4..cc140573 100644 --- a/parse-todo-of-stacked-rebase/validator.ts +++ b/parse-todo-of-stacked-rebase/validator.ts @@ -267,7 +267,7 @@ type BadCommand = { reasons: string[]; }; -export type GoodCommand = { +export type GoodCommandBase = { commandOrAliasName: EitherRebaseEitherCommandOrAlias; lineNumber: number; fullLine: string; @@ -280,17 +280,17 @@ export type GoodCommand = { index: number; // commandName: EitherRebaseCommand; -} & ( - | { - rebaseKind: "regular"; - commandName: RegularRebaseCommand; - } - | { - rebaseKind: "stacked"; - commandName: StackedRebaseCommand; - commitSHAThatBranchPointsTo: string | null; - } -); +}; +export type GoodCommandRegular = GoodCommandBase & { + rebaseKind: "regular"; + commandName: RegularRebaseCommand; +}; +export type GoodCommandStacked = GoodCommandBase & { + rebaseKind: "stacked"; + commandName: StackedRebaseCommand; + commitSHAThatBranchPointsTo: string | null; +}; +export type GoodCommand = GoodCommandRegular | GoodCommandStacked; export function validate( linesOfEditedRebaseTodo: string[], // From 205b841da94d726ecddf2d591904fcd52b8c3f09 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Tue, 22 Mar 2022 02:51:59 +0200 Subject: [PATCH 27/34] =?UTF-8?q?feat:=20implement=20custom=20`getBoundari?= =?UTF-8?q?esInclInitial`=20for=20`forcePush`,=20using=20only=20existing?= =?UTF-8?q?=20commits=20=F0=9F=94=A5=F0=9F=94=A5=F0=9F=94=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Kipras Melnikovas --- branchSequencer.ts | 9 +++++++-- forcePush.ts | 17 +++++++++++++++++ git-stacked-rebase.ts | 36 ++++++++++++++++++++++++++---------- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/branchSequencer.ts b/branchSequencer.ts index dba8ca63..264efa62 100644 --- a/branchSequencer.ts +++ b/branchSequencer.ts @@ -20,7 +20,9 @@ export type SimpleBranchAndCommit = { branchEndFullName: string; // branchExistsYet: boolean; // TODO }; -export type GetBoundariesInclInitial = (ctx: GetBranchesCtx) => SimpleBranchAndCommit[]; +export type GetBoundariesInclInitial = ( + ctx: GetBranchesCtx // +) => SimpleBranchAndCommit[] | Promise; const defautlGetBoundariesInclInitial: GetBoundariesInclInitial = ({ pathToStackedRebaseDirInsideDotGit, // @@ -116,6 +118,9 @@ export type BranchSequencerArgsBase = { repo: Git.Repository; rootLevelCommandName: string; gitCmd: string; + // + initialBranch: Git.Reference; + currentBranch: Git.Reference; }; export type BranchSequencerArgs = BranchSequencerArgsBase & { // callbackBeforeBegin?: CallbackAfterDone; // TODO @@ -144,7 +149,7 @@ export const branchSequencer: BranchSequencer = async ({ }) => { const execSyncInRepo = createExecSyncInRepo(repo); - const branchesAndCommits: SimpleBranchAndCommit[] = getBoundariesInclInitial({ + const branchesAndCommits: SimpleBranchAndCommit[] = await getBoundariesInclInitial({ pathToStackedRebaseDirInsideDotGit, pathToStackedRebaseTodoFile, repo, diff --git a/forcePush.ts b/forcePush.ts index a7c0befa..aca88579 100644 --- a/forcePush.ts +++ b/forcePush.ts @@ -2,9 +2,11 @@ // import fs from "fs"; +import { getWantedCommitsWithBranchBoundariesOurCustomImpl } from "./git-stacked-rebase"; import { branchSequencer, // BranchSequencerBase, + SimpleBranchAndCommit, // getBackupPathOfPreviousStackedRebase, } from "./branchSequencer"; @@ -38,4 +40,19 @@ export const forcePush: BranchSequencerBase = (argsBase) => execSyncInRepo(`${argsBase.gitCmd} push --force`); }, delayMsBetweenCheckouts: 0, + getBoundariesInclInitial: () => + getWantedCommitsWithBranchBoundariesOurCustomImpl( + argsBase.repo, // + argsBase.initialBranch, + argsBase.currentBranch + ).then((boundaries) => + boundaries + .filter((b) => !!b.branchEnd) + .map( + (boundary): SimpleBranchAndCommit => ({ + branchEndFullName: boundary.branchEnd!.name(), // TS ok because of the filter + commitSHA: boundary.commit.sha(), + }) + ) + ), }); diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 593e12b4..fcd04047 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -289,6 +289,19 @@ export const gitStackedRebase = async ( const checkIsRegularRebaseStillInProgress = (): boolean => fs.existsSync(pathToRegularRebaseDirInsideDotGit); + fs.mkdirSync(pathToStackedRebaseDirInsideDotGit, { recursive: true }); + + const initialBranch: Git.Reference | void = await Git.Branch.lookup( + repo, // + nameOfInitialBranch, + Git.Branch.BRANCH.ALL + ); + if (!initialBranch) { + throw new Error("initialBranch lookup failed"); + } + + const currentBranch: Git.Reference = await repo.getCurrentBranch(); + if (fs.existsSync(path.join(pathToStackedRebaseDirInsideDotGit, filenames.willNeedToApply))) { _markThatNeedsToApply(pathToStackedRebaseDirInsideDotGit); } @@ -300,6 +313,8 @@ export const gitStackedRebase = async ( pathToStackedRebaseDirInsideDotGit, // rootLevelCommandName: "--apply", gitCmd: options.gitCmd, + initialBranch, + currentBranch, }); } @@ -327,6 +342,8 @@ export const gitStackedRebase = async ( gitCmd: options.gitCmd, autoApplyIfNeeded: configValues.autoApplyIfNeeded, config, + initialBranch, + currentBranch, }); return; @@ -340,6 +357,8 @@ export const gitStackedRebase = async ( gitCmd: options.gitCmd, autoApplyIfNeeded: configValues.autoApplyIfNeeded, config, + initialBranch, + currentBranch, }); if (neededToApply && !userAllowedToApplyAndWeApplied) { @@ -357,6 +376,8 @@ export const gitStackedRebase = async ( pathToStackedRebaseDirInsideDotGit, rootLevelCommandName: "--push --force", gitCmd: options.gitCmd, + initialBranch, + currentBranch, }); } @@ -371,6 +392,8 @@ export const gitStackedRebase = async ( actionInsideEachCheckedOutBranch: ({ execSyncInRepo: execS }) => (execS(toExec), void 0), pathToStackedRebaseDirInsideDotGit, pathToStackedRebaseTodoFile, + initialBranch, + currentBranch, }); } else { /** @@ -382,15 +405,6 @@ export const gitStackedRebase = async ( } } - fs.mkdirSync(pathToStackedRebaseDirInsideDotGit, { recursive: true }); - - const initialBranch: Git.Reference | void = await Git.Branch.lookup( - repo, // - nameOfInitialBranch, - Git.Branch.BRANCH.ALL - ); - const currentBranch: Git.Reference = await repo.getCurrentBranch(); - const wasRegularRebaseInProgress: boolean = checkIsRegularRebaseStillInProgress(); // const @@ -900,6 +914,8 @@ mv -f "${preparedRegularRebaseTodoFile}" "${pathToRegularRebaseTodoFile}" gitCmd: options.gitCmd, autoApplyIfNeeded: configValues.autoApplyIfNeeded, config, + initialBranch, + currentBranch, }); } } @@ -1086,7 +1102,7 @@ type CommitAndBranchBoundary = { branchEnd: Git.Reference | null; }; -async function getWantedCommitsWithBranchBoundariesOurCustomImpl( +export async function getWantedCommitsWithBranchBoundariesOurCustomImpl( repo: Git.Repository, // /** beginningBranch */ bb: Git.Reference, From 2b712d85f6343f09adf1ddec73076128229e4a03 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Sun, 20 Mar 2022 04:20:31 +0200 Subject: [PATCH 28/34] fix: create the .git/stacked-rebase/ dir only right before starting our rebase Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index fcd04047..2c0e7687 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -289,8 +289,6 @@ export const gitStackedRebase = async ( const checkIsRegularRebaseStillInProgress = (): boolean => fs.existsSync(pathToRegularRebaseDirInsideDotGit); - fs.mkdirSync(pathToStackedRebaseDirInsideDotGit, { recursive: true }); - const initialBranch: Git.Reference | void = await Git.Branch.lookup( repo, // nameOfInitialBranch, @@ -414,6 +412,14 @@ export const gitStackedRebase = async ( throw new Termination("regular rebase already in progress"); } + /** + * only create the dir now, when it's needed. + * otherwise, other commands can incorrectly infer + * that our own stacked rebase is in progress, + * when it's not, up until now. + */ + fs.mkdirSync(pathToStackedRebaseDirInsideDotGit, { recursive: true }); + await createInitialEditTodoOfGitStackedRebase( repo, // initialBranch, From 15ba7205cd9de349b0dd1916812f04ee1d794fa1 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Mon, 21 Mar 2022 22:02:05 +0200 Subject: [PATCH 29/34] misc: fix the `main` entrypoint in package.json Signed-off-by: Kipras Melnikovas --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06c6e78a..5ddc1ccb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "git-stacked-rebase", "version": "0.6.1", - "main": "index.js", + "main": "dist/git-stacked-rebase.js", "repository": "git@github.com:kiprasmel/git-stacked-rebase.git", "author": "Kipras Melnikovas (https://kipras.org/)", "license": "UNLICENSED", From 09a278d372950816c4e97ce25e4b7389a6e50814 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Tue, 22 Mar 2022 05:05:29 +0200 Subject: [PATCH 30/34] extend merging logic in `combineRewrittenLists` after doing a big rebase Signed-off-by: Kipras Melnikovas --- reducePath.ts | 106 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 4 deletions(-) diff --git a/reducePath.ts b/reducePath.ts index 0e63afdc..2fcbdba4 100755 --- a/reducePath.ts +++ b/reducePath.ts @@ -139,16 +139,114 @@ export function combineRewrittenLists(rewrittenListFileContent: string): Combine } } else { if (Object.values(list.mapping).includes(key)) { + if (Object.values(list.mapping).includes(value)) { + + console.warn(`value already in values`, { + [key]: value, + [Object.entries(list.mapping).find(([_k, v]) => v === value)![0]]: value, + }) + // continue + // throw; + + /** + * happened when: + * mark "edit" on commit A and B, + * reach commit A, + * do git commit --amend to change the title, + * continue to commit B, + * stop because of the another "edit", + * reset to HEAD~ (commit A) (changes kept in workdir), + * add all changes, + * git commit --amend them into commit A. + * + * how things ended up in the rewritten-list, was that: + * + * amend + * TMP_SHA -> NEW_SHA + * + * rebase + * COMMIT_A_SHA -> TMP_SHA + * COMMIT_B_SHA -> NEW_SHA + * + * + * and would end up as + * + * COMMIT_A_SHA -> NEW_SHA + * COMMIT_B_SHA -> NEW_SHA + * + * from our `git-rebase-todo` file, the ~~OLD_SHA_2~~ COMMIT_B_SHA was the original one found, + * BUT, it pointed to commit B, not commit A! + * + * there were more mappings in the rewritten-list that included the commit A's SHA... + * this is getting complicated. + * + * ---rm + * the 1st mapping of TMP_SHA -> NEW_SHA ended up first in the rewritten-list inside an "amend". + * the 2nd mapping of OLD_SHA_2 -> NEW_SHA ended up second in the rewritten-list inside the "rebase". + * --- + * + * + * TODO needs more testing. + * + * i mean, we very well could just get rid of the key->value pair + * if there exists another one with the same value, + * but how do we know which key to keep? + * + * wait... you keep the earliest key? + * + */ + // fwiw, i don't think this algo makes you keep the earliest key (or does it?) + Object.entries(list.mapping).forEach(([k, v]) => { + if (v === value && k !== key) { + // if it's not our key, delete it + // (our key will get assigned a new value below.) + console.info("deleting entry because duplicate A->B, C->A, D->B, ends up C->B, D->B, keeping only one", { + [k]: list.mapping[k], + }) + delete list.mapping[k] + } + }) + /** + * TODO test if you "fixup" (reset, add, amend) first -- + * does this reverse the order & you'd need the last key? + * + * TODO what if you did both and you need a key from the middle? lol + * + */ + } + /** * add the single new entry of amend's mapping into rebase's mapping. * it will get `reducePath`'d later. */ Object.assign(list.mapping, amend.mapping) } else { - throw new Error( - "NOT IMPLEMENTED - neither key nor value of 'amend' was included in the 'rebase'." - + "could be that we missed the ordering, or when we call 'reducePath', or something else." - ) + if (Object.values(list.mapping).includes(value)) { + /** + * TODO needs more testing. + * especially which one is the actually newer one -- same questions apply as above. + */ + console.warn("the `rebase`'s mapping got a newer value than the amend, apparently. continuing.", { + [key]: value, + [Object.entries(list.mapping).find(([_k, v]) => v === value)![0]]: value, + }) + continue + } else { + console.warn( + "NOT IMPLEMENTED - neither key nor value of 'amend' was included in the 'rebase'." + + "\ncould be that we missed the ordering, or when we call 'reducePath', or something else.", + { + [key]: value, + }) + + /** + * i think this happens when commit gets rewritten, + * then amended, and amended again. + * + * looks like it's fine to ignore it. + */ + continue + } } } } From 3275f2533636c2a6dc8412323c4511913d11b219 Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Mon, 28 Mar 2022 18:18:56 +0300 Subject: [PATCH 31/34] reducePath extras Signed-off-by: Kipras Melnikovas --- reducePath.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/reducePath.ts b/reducePath.ts index 2fcbdba4..b63c2448 100755 --- a/reducePath.ts +++ b/reducePath.ts @@ -132,10 +132,34 @@ export function combineRewrittenLists(rewrittenListFileContent: string): Combine // pointless continue } else { - throw new Error( - `NOT IMPLEMENTED - identical key in 'amend' and 'rebase', but different values.` - + `(key = "${key}", amend's value = "${value}", rebase's value = "${list.mapping[key]}")` - ) + //throw new Error( + // `NOT IMPLEMENTED - identical key in 'amend' and 'rebase', but different values.` + //+ `(key = "${key}", amend's value = "${value}", rebase's value = "${list.mapping[key]}")` + //) + + /** + * amend + * A->B + * + * rebase + * A->C + * + * + * hmm. + * will we need to keep track of _when_ the post-rewrite happened as well? + * (i.e. on what commit) + * though, idk if that's possible, i think i already tried, + * but since the post-rewrite script is called _after_ the amend/rebase happens, + * it gives you the same commit that you already have, + * i.e. the already rewritten one, instead of the previous one... + * + */ + + /** + * for starters, we can try always favoring the amend over rebase + */ + Object.assign(list.mapping, amend.mapping) + } } else { if (Object.values(list.mapping).includes(key)) { From b511d2f3c480a56c92c8145e0e1e1d098be4a0fd Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Wed, 23 Mar 2022 00:57:18 +0200 Subject: [PATCH 32/34] remove docs for --continue since not really needed anymore Signed-off-by: Kipras Melnikovas --- git-stacked-rebase.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/git-stacked-rebase.ts b/git-stacked-rebase.ts index 2c0e7687..c23a7935 100755 --- a/git-stacked-rebase.ts +++ b/git-stacked-rebase.ts @@ -1426,16 +1426,6 @@ git-stacked-rebase [-a|--apply] 2. but wil not push the partial branches to a remote until --push --force is used. -git-stacked-rebase [] (-c|--continue) - - (!) should be used instead of git-rebase's --continue - - ...because, additionally to invoking git rebase --continue, - this option automatically (prompts you to) --apply (if the rebase - has finished), thus ensuring that the partial branches - do not go out of sync with the newly rewritten history. - - git-stacked-rebase [--push|-p --force|-f] 1. will checkout each branch and will push --force, From 8e1439c2767dfb693a18a7b4c29173f1350da62e Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Mon, 11 Apr 2022 04:18:34 +0300 Subject: [PATCH 33/34] fix: handle leftover `prev` amends if no further rebase happened Signed-off-by: Kipras Melnikovas --- reducePath.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/reducePath.ts b/reducePath.ts index b63c2448..48113d77 100755 --- a/reducePath.ts +++ b/reducePath.ts @@ -112,6 +112,8 @@ export function combineRewrittenLists(rewrittenListFileContent: string): Combine let prev : RewrittenListBlockAmend[] = [] let mergedReducedRewrittenLists: RewrittenListBlockRebase[] = [] + let lastRebaseList: RewrittenListBlockRebase | null = null + for (const list of rewrittenLists) { if (list.type === "amend") { prev.push(list) @@ -278,10 +280,35 @@ export function combineRewrittenLists(rewrittenListFileContent: string): Combine prev = [] reducePath(list.mapping) mergedReducedRewrittenLists.push(list) + lastRebaseList = list } else { throw new Error(`invalid list type (got "${(list as any).type}")`) } } + + if (prev.length) { + /** + * likely a rebase happenend first, + * it was not `--apply`ied, + * and then a `commit --amend` happend. + * + * if we don't handle this case, + * the changes done in the `--amend` + * would be lost. + */ + + if (!lastRebaseList) { + throw new Error(`NOT IMPLEMENTED - found "amend"(s) in rewritten-list, but did not find any "rebase"(s).`) + } + + for (const amend of prev) { + Object.assign(lastRebaseList.mapping, amend.mapping) + reducePath(lastRebaseList.mapping) + } + + prev = [] + } + /** * TODO handle multiple rebases * or, multiple separate files for each new rebase, From 7ae062947e055763fc3782e5698e7c267445d50d Mon Sep 17 00:00:00 2001 From: Kipras Melnikovas Date: Mon, 11 Apr 2022 04:24:56 +0300 Subject: [PATCH 34/34] fix: use the *last* rebase list, instead of the first a) if the was only 1 rebase, nothing changes b) in other cases i've encountered, using the last rebase always proved better than the 1st one: 1st one would error oftenly; succeeded rarily last one would work just fine but, there's still a lot of testing to do. especially for >2 rebases, since i've only encountered 2. it's a bit dangerous because if we pick the wrong rebase, then we could lose commits / their content. now, picking just 1 rebase is still incomplete -- this fix is just a (seemingly) better heuristic, (!) but eventually we'll need to take into account all of the rebases. there might be ways to eliminate this need, e.g. auto-calling `git-stacked-rebase --apply` in the `post-rewrite` script in the background, but at least for now, don't want to mess with it and would prefer to avoid, until i have done more dog-fooding & encounter more edge-cases & maybe potential solutions/invariants as well. Signed-off-by: Kipras Melnikovas --- reducePath.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/reducePath.ts b/reducePath.ts index 48113d77..66c36b41 100755 --- a/reducePath.ts +++ b/reducePath.ts @@ -307,7 +307,11 @@ export function combineRewrittenLists(rewrittenListFileContent: string): Combine } prev = [] - } + } + + if (!lastRebaseList) { + throw new Error(`NOT IMPLEMENTED - did not find any "rebase"(s).`) + } /** * TODO handle multiple rebases @@ -317,7 +321,7 @@ export function combineRewrittenLists(rewrittenListFileContent: string): Combine console.log("mergedReducedRewrittenLists", mergedReducedRewrittenLists) - const combinedRewrittenList = Object.entries(mergedReducedRewrittenLists[0].mapping).map(([k, v]) => k + " " + v).join("\n") + "\n" + const combinedRewrittenList = Object.entries(lastRebaseList.mapping).map(([k, v]) => k + " " + v).join("\n") + "\n" // fs.writeFileSync("rewritten-list", combinedRewrittenList) return {