diff --git a/rhino/tools/git/commands.fifty.ts b/rhino/tools/git/commands.fifty.ts index 7e7252a81..32c3c772a 100644 --- a/rhino/tools/git/commands.fifty.ts +++ b/rhino/tools/git/commands.fifty.ts @@ -71,31 +71,57 @@ namespace slime.jrunscript.tools.git { verify(status).evaluate.property("paths").is(void(0)); }; - /** - * Verifies the behavior of various operations that affect the git staging area. - */ - fifty.tests.world.staging = function() { - var commit: slime.jrunscript.tools.git.Command<{ - message: string - },void> = { - invocation: function(p) { - return { - command: "commit", - arguments: $api.Array.build(function(rv) { - rv.push("--message", p.message); - }) - } + var commit: slime.jrunscript.tools.git.Command<{ + message: string + },void> = { + invocation: function(p) { + return { + command: "commit", + arguments: $api.Array.build(function(rv) { + rv.push("--message", p.message); + }) } - }; + } + }; - var reset: slime.jrunscript.tools.git.Command = { - invocation: function(p) { - return { - command: "reset" - } + var reset: slime.jrunscript.tools.git.Command = { + invocation: function(p) { + return { + command: "reset" } - }; + } + }; + var rename: slime.jrunscript.tools.git.Command<{ from: string, to: string },void> = { + invocation: function(p) { + return { + command: "mv", + arguments: [p.from, p.to] + } + } + }; + + fifty.tests.exports.status.rename = function() { + var it = fixtures.empty(); + + fixtures.edit(it, "a", function(before) { return "a"; }); + it.api.command(add).argument("a").run(); + it.api.command(commit).argument({ message: "a" }).run(); + + it.api.command(rename).argument({ from: "a", to: "b" }).run(); + + var status = it.api.command(jsh.tools.git.commands.status).argument().run(); + verify(status).entries.length.is(1); + verify(status).entries[0].code.is("R "); + verify(status).entries[0].path.is("b"); + verify(status).entries[0].orig_path.is("a"); + jsh.shell.console(JSON.stringify(status)); + }; + + /** + * Verifies the behavior of various operations that affect the git staging area. + */ + fifty.tests.world.staging = function() { function status() { return it.api.command(jsh.tools.git.commands.status).argument().run(); } diff --git a/rhino/tools/git/commands.js b/rhino/tools/git/commands.js index 1ab77e27f..c5fbce9fa 100644 --- a/rhino/tools/git/commands.js +++ b/rhino/tools/git/commands.js @@ -27,8 +27,13 @@ }, result: function(output) { // TODO This ignores renamed files; see git help status + /** + * @type { slime.jrunscript.tools.git.command.status.Result } + */ var rv = { - branch: void(0) + branch: void(0), + entries: [], + paths: void(0) }; output.split("\n").forEach(function(line) { var NO_COMMITS_PREFIX = "## No commits yet on "; @@ -42,11 +47,21 @@ var detached = Boolean(branchName == "HEAD (no branch)") rv.branch = (detached) ? null : branchName; } else { - var parser = /(..) (\S+)/; + var parser = /(..) (\S+)(?: -> (\S+))?/; var match = parser.exec(line); if (match) { - if (!rv.paths) rv.paths = {}; - rv.paths[match[2]] = match[1]; + if (match[3]) { + rv.entries.push({ + code: match[1], + path: match[3], + orig_path: match[2] + }); + } else { + rv.entries.push({ + code: match[1], + path: match[2] + }); + } } else if (line == "") { // do nothing } else { @@ -54,6 +69,13 @@ } } }); + rv.paths = rv.entries.reduce(function(rv,entry) { + if (typeof(rv) == "undefined") { + rv = {}; + } + rv[entry.path] = entry.code; + return rv; + }, rv.paths); return rv; } } diff --git a/rhino/tools/git/module.fifty.ts b/rhino/tools/git/module.fifty.ts index b2ed3b0d0..cc57678e8 100644 --- a/rhino/tools/git/module.fifty.ts +++ b/rhino/tools/git/module.fifty.ts @@ -758,12 +758,24 @@ namespace slime.jrunscript.tools.git { */ branch: string + entries: { + code: string + path: string + orig_path?: string + }[] + /** + * @deprecated Replaced by the `entries` property, which properly captures rename entries. + * * An object whose keys are string paths within the repository, and whose values are the two-letter output * of the `git status --porcelain` command. This property is absent if no files have a status. */ paths?: { [path: string]: string } } + + export namespace old { + export type Result = Pick + } } export interface Exports { diff --git a/tools/wf/plugin-standard.jsh.fifty.ts b/tools/wf/plugin-standard.jsh.fifty.ts index 81d629e1e..6d9578484 100644 --- a/tools/wf/plugin-standard.jsh.fifty.ts +++ b/tools/wf/plugin-standard.jsh.fifty.ts @@ -32,6 +32,10 @@ namespace slime.jsh.wf { function fixture() { var project = fifty.jsh.file.object.temporary.location(); fifty.jsh.file.object.getRelativePath("test/data/plugin-standard").directory.copy(project); + + // Add a sample file to the fixture + project.directory.getRelativePath("a.js").write("", { append: false }); + var repository = jsh.tools.git.init({ pathname: project }); @@ -391,17 +395,18 @@ namespace slime.jsh.wf { } }; + var fixtures = { + git: ( + function() { + var script: slime.jrunscript.tools.git.test.fixtures.Script = fifty.$loader.script("../../rhino/tools/git/fixtures.ts"); + var setup = script(); + return setup(fifty); + } + )(), + subject: test.fixtures + }; + fifty.tests.issue485 = function() { - var fixtures = { - git: ( - function() { - var script: slime.jrunscript.tools.git.test.fixtures.Script = fifty.$loader.script("../../rhino/tools/git/fixtures.ts"); - var setup = script(); - return setup(fifty); - } - )(), - subject: test.fixtures - }; var cloned = fixtures.subject.project(); var repository = toGitFixturesRepository(cloned); jsh.shell.world.run( @@ -425,26 +430,42 @@ namespace slime.jsh.wf { } catch (e) { success = false; } - // var result = jsh.shell.run({ - // command: cloned.directory.getRelativePath("slime/tools/wf.bash").toString(), - // arguments: ["commit", "--message", "test"], - // directory: cloned.directory, - // // TODO add access to $api.Object in Fifty tests - // environment: Object.assign({}, - // jsh.shell.environment, - // { PROJECT: cloned.directory.toString() } - // ), - // // stdio: { - // // output: String, - // // error: String - // // }, - // evaluate: function(result) { return result; } - // }); - // verify(result).status.is(1); verify(success).is(false); var after = repository.api.command(jsh.tools.git.commands.status).argument().run(); verify(after).paths["wf.js"].is(" M"); } + + fifty.tests.issue174 = function() { + var mv: slime.jrunscript.tools.git.Command<{ from: string, to: string },void> = { + invocation: function(p) { + return { + command: "mv", + arguments: [p.from, p.to] + } + } + }; + + var project = toGitFixturesRepository(fixtures.subject.project()); + + jsh.shell.world.run( + jsh.shell.Invocation.create({ + command: $api.Function.result(project.location, jsh.file.world.Location.relative("wf")).pathname, + arguments: ["status"] + }) + )({ + }); + + fixtures.git.edit(project, "new.js", () => "new"); + project.api.command(mv).argument({ from: "a.js", to: "b.js" }).run(); + + jsh.shell.world.run( + jsh.shell.Invocation.create({ + command: $api.Function.result(project.location, jsh.file.world.Location.relative("wf")).pathname, + arguments: ["status"] + }) + )({ + }); + } } //@ts-ignore )(fifty); diff --git a/tools/wf/plugin-standard.jsh.js b/tools/wf/plugin-standard.jsh.js index 31f54119b..595358daf 100644 --- a/tools/wf/plugin-standard.jsh.js +++ b/tools/wf/plugin-standard.jsh.js @@ -195,26 +195,32 @@ $exports.status = function(p) { // TODO add option for offline - var repository = jsh.wf.git.fetch(); + var oRepository = jsh.wf.git.fetch(); + var fRepository = jsh.tools.git.program({ command: "git" }).repository(oRepository.directory.toString()); var remote = "origin"; - var status = repository.status(); - var branch = status.branch.name; + var status = fRepository.command(jsh.tools.git.commands.status).argument().run(); + var branch = status.branch; var base = remote + "/" + branch; - if (!branchExists(repository.directory.toString(), base)) { + if (!branchExists(oRepository.directory.toString(), base)) { base = "origin/master"; } - var vsRemote = (branch) ? jsh.wf.git.compareTo(base)(repository) : null; - jsh.shell.console("Current branch: " + displayBranchName(status.branch.name)); + var vsRemote = (branch) ? jsh.wf.git.compareTo(base)(oRepository) : null; + jsh.shell.console("Current branch: " + displayBranchName(status.branch)); if (vsRemote && vsRemote.ahead.length) jsh.shell.console("ahead of " + base + ": " + vsRemote.ahead.length); if (vsRemote && vsRemote.behind.length) jsh.shell.console("behind " + base + ": " + vsRemote.behind.length); var output = $api.Function.result( - status.paths, + status.entries, $api.Function.conditional({ - condition: Boolean, + condition: function(entries) { + return entries.length > 0; + }, true: $api.Function.pipe( - Object.entries, $api.Function.Array.map(function(entry) { - return entry[1] + " " + entry[0]; + if (entry.orig_path) { + return entry.code + " " + entry.path + " (was: " + entry.orig_path + ")"; + } else { + return entry.code + " " + entry.path; + } }), $api.Function.Array.join("\n") ), @@ -224,15 +230,15 @@ if (output) jsh.shell.console(output); if (vsRemote && vsRemote.behind.length && !vsRemote.ahead.length && !vsRemote.paths) { jsh.shell.console("Fast-forwarding ..."); - repository.merge({ ffOnly: true, name: base }); + oRepository.merge({ ffOnly: true, name: base }); } - var branches = repository.branch({ remote: true, all: true }); + var branches = oRepository.branch({ remote: true, all: true }); var first = true; branches.forEach(function findUnmergedBranches(branch) { if (branch.name === null) { return; } else { - var compared = jsh.wf.git.compareTo(branch.name)(repository); + var compared = jsh.wf.git.compareTo(branch.name)(oRepository); if (compared.behind.length) { if (first) { jsh.shell.console(""); @@ -245,7 +251,7 @@ if (!first) { jsh.shell.console(""); } - if (repository.submodule().length) { + if (oRepository.submodule().length) { jsh.shell.console(""); jsh.shell.console("Submodules:"); var submodules = jsh.wf.project.submodule.status(); @@ -482,7 +488,7 @@ } ) : void(0), remove: $api.Function.pipe( - $api.Function.impure.revise(jsh.script.cli.option.string({ longname: "path" })), + jsh.script.cli.option.string({ longname: "path" }), function(p) { var path = p.options.path; jsh.wf.project.submodule.remove({ path: path });