Skip to content

Commit 01a8ddf

Browse files
Merge pull request #71 from MichaelCurrin/fix-handle-filenames-with-spaces
fix: Handle filenames with spaces
2 parents 2fd4405 + adaaaa9 commit 01a8ddf

12 files changed

Lines changed: 316 additions & 55 deletions

src/generate/action.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* differently.
99
*/
1010
import { ACTION, ROOT } from "../lib/constants";
11-
import { moveOrRenameFromPaths, splitPath } from "../lib/paths";
11+
import { moveOrRenameFromPaths, quoteForSpaces, splitPath } from "../lib/paths";
1212
import { ActionKeys } from "./action.d";
1313

1414
/**
@@ -35,13 +35,19 @@ export function moveOrRenameMsg(oldPath: string, newPath: string): string {
3535

3636
let msg;
3737

38+
const from = quoteForSpaces(oldP.name);
39+
3840
if (moveDesc === "move") {
39-
msg = `move ${oldP.name} to ${newP.dirPath}`;
41+
const to = quoteForSpaces(newP.dirPath);
42+
msg = `move ${from} to ${to}`;
4043
} else if (moveDesc === "rename") {
41-
msg = `rename ${oldP.name} to ${newP.name}`;
44+
const to = quoteForSpaces(newP.name);
45+
msg = `rename ${from} to ${to}`;
4246
} else {
43-
const target = newP.dirPath === ROOT ? `${newP.name} at ${ROOT}` : newPath;
44-
msg = `move and rename ${oldP.name} to ${target}`;
47+
const to = quoteForSpaces(newP.name);
48+
const target =
49+
newP.dirPath === ROOT ? `${to} at ${ROOT}` : quoteForSpaces(newPath);
50+
msg = `move and rename ${from} to ${target}`;
4551
}
4652

4753
return msg;

src/generate/message.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,10 @@ export function oneChange(line: string) {
4242
/**
4343
* Describe an action and paths for a set of changed files.
4444
*
45-
* @param lines Expect one or more lines that came from a `git` subcommand.
45+
* @param lines Expect one or more lines that came from a Git subcommand.
4646
*
47-
* @return A human-readable sentence decribing file changes. e.g. 'update foo.txt and bar.txt'.
47+
* @return A human-readable sentence decribing file changes. e.g. 'update
48+
* foo.txt and bar.txt'.
4849
*/
4950
export function namedFilesDesc(changes: FileChange[]) {
5051
const actions = changes.map(item => item.x as ActionKeys);

src/git/parseOutput.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ export function parseDiffIndex(line: string): FileChange {
6262
const x = line[0];
6363
const y = UNMODIFIED;
6464

65-
const [_, from, to] = line.split(/\s+/);
65+
const [_, from, to] = line.split("\t");
6666
if (!from) {
6767
// Unlikely in real life, but this helps in development.
6868
throw new Error(`Invalid input - could not find 'from' path: ${line}`);

src/lib/paths.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import * as path from "path";
77
import { ROOT } from "../lib/constants";
88
import { MoveOrRename, SplitPathResult } from "./paths.d";
99

10-
// The starts of filenames which could be repeated in a repo. All lowercase
11-
// here.
10+
// The starts of filenames which might be repeated as files in a repo. Kept as
11+
// all lowercase here.
1212
const REPEAT_FILENAMES = ["readme", "index", "__init__.py"];
1313

1414
/**
@@ -36,6 +36,15 @@ export function splitPath(filePath: string): SplitPathResult {
3636
};
3737
}
3838

39+
/** Format to add quotes if the values contains spaces. */
40+
export function quoteForSpaces(value: string) {
41+
if (value.includes(" ") && value !== ROOT) {
42+
return `'${value}'`;
43+
}
44+
45+
return value;
46+
}
47+
3948
/**
4049
* Change file path to a more readable format.
4150
*
@@ -50,10 +59,10 @@ export function friendlyFile(filePath: string) {
5059

5160
for (const p of REPEAT_FILENAMES) {
5261
if (nameLower.startsWith(p)) {
53-
return filePath;
62+
return quoteForSpaces(filePath);
5463
}
5564
}
56-
return name;
65+
return quoteForSpaces(name);
5766
}
5867

5968
/**

src/test/generate/action.test.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,33 +39,58 @@ describe("Desribe a file using two paths", function () {
3939
moveOrRenameMsg("foo.txt", "bar.txt"),
4040
"rename foo.txt to bar.txt"
4141
);
42+
4243
assert.strictEqual(
4344
moveOrRenameMsg("buzz/foo.txt", "buzz/bar.txt"),
4445
"rename foo.txt to bar.txt"
4546
);
47+
48+
assert.strictEqual(
49+
moveOrRenameMsg("fizz buzz/foo.txt", "fizz buzz/bar.txt"),
50+
"rename foo.txt to bar.txt"
51+
);
4652
});
4753

4854
it("can describe a moved file", function () {
4955
assert.strictEqual(
5056
moveOrRenameMsg("buzz/foo.txt", "fizz/foo.txt"),
5157
"move foo.txt to fizz"
5258
);
59+
60+
assert.strictEqual(
61+
moveOrRenameMsg("buzz/foo bar.txt", "fizz/foo bar.txt"),
62+
"move 'foo bar.txt' to fizz"
63+
);
64+
5365
assert.strictEqual(
5466
moveOrRenameMsg("buzz/foo.txt", "foo.txt"),
5567
"move foo.txt to repo root"
5668
);
69+
70+
assert.strictEqual(
71+
moveOrRenameMsg("buzz/foo bar.txt", "foo bar.txt"),
72+
"move 'foo bar.txt' to repo root"
73+
);
5774
});
5875

5976
it("can describe a remamed and moved file", function () {
6077
assert.strictEqual(
6178
moveOrRenameMsg("foo.txt", "fizz/bar.txt"),
6279
"move and rename foo.txt to fizz/bar.txt"
6380
);
64-
// This is a rare case, so don't bother trying to handle it smarter around paths.
81+
82+
// This is a rare case, so don't bother trying to handle it smarter around
83+
// paths.
6584
assert.strictEqual(
6685
moveOrRenameMsg("fuzz/foo.txt", "fizz/bar.txt"),
6786
"move and rename foo.txt to fizz/bar.txt"
6887
);
88+
89+
assert.strictEqual(
90+
moveOrRenameMsg("fuzz/foo.txt", "fizz/bar bazz.txt"),
91+
"move and rename foo.txt to 'fizz/bar bazz.txt'"
92+
);
93+
6994
assert.strictEqual(
7095
moveOrRenameMsg("fizz/foo.txt", "bar.txt"),
7196
"move and rename foo.txt to bar.txt at repo root"

src/test/generate/convCommit.test.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ describe("Test #ConventionalCommit class for path-based conventional commit logi
8181
});
8282

8383
describe("#isBuildRelated", function () {
84-
it("can recognize a build change fr a build-related filename", function () {
84+
it("can recognize a build change for a build-related filename", function () {
8585
assert.strictEqual(
8686
new ConventionalCommit("Dockerfile").isBuildRelated(),
8787
true
@@ -186,10 +186,11 @@ describe("Test #ConventionalCommit class for path-based conventional commit logi
186186
});
187187

188188
describe("#getType", function () {
189-
// Rather than true and false like in above tests this actually categorizes and also it closer
190-
// to the real world as it through a hierarchy (for example .yml is config-related unless it is
191-
// for a CI file). But, this doesn't care what the action is like create or delete or modify, so
192-
// it won't impose meaning based on that.
189+
// Rather than true and false like in above tests this actually categorizes
190+
// and also it closer to the real world as it through a hierarchy (for
191+
// example .yml is config-related unless it is for a CI file). But, this
192+
// doesn't care what the action is like create or delete or modify, so it
193+
// won't impose meaning based on that.
193194
it("sees a build file as build", function () {
194195
assert.strictEqual(
195196
new ConventionalCommit("Makefile").getType(),
@@ -232,7 +233,7 @@ describe("Test #ConventionalCommit class for path-based conventional commit logi
232233
);
233234
});
234235

235-
// TODO Break into categories
236+
// TODO: Break into categories
236237
it("can tell a type for other types", function () {
237238
assert.strictEqual(
238239
new ConventionalCommit("foo").getType(),

src/test/generate/count.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,22 @@ describe("Aggregate counts of files as numeric data", function () {
3030
assert.deepStrictEqual(_countByAction(changes), expected);
3131
});
3232

33+
it("should handle a created file with spaces", function () {
34+
const changes: FileChange[] = [
35+
{
36+
x: "A",
37+
y: " ",
38+
from: "foo bar.txt",
39+
to: "",
40+
},
41+
];
42+
const expected = {
43+
create: { fileCount: 1 },
44+
};
45+
46+
assert.deepStrictEqual(_countByAction(changes), expected);
47+
});
48+
3349
it("should handle an updated file", function () {
3450
const changes: FileChange[] = [
3551
{

src/test/generate/message.test.ts

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,20 +10,27 @@ import { namedFilesDesc, oneChange } from "../../generate/message";
1010

1111
describe("Generate commit message for a single changed file", function () {
1212
// Notes:
13-
// - The command `git status --short` expects XY format but this is for `git
14-
// diff-index` which is only X. Also there is just spaces between - no
15-
// '->' symbol.
13+
// - The command `git status --short` expects `XY` format but this is for
14+
// `git diff-index` which is only `X`. Also there is just spaces between -
15+
// no '->' symbol.
1616
// - Impossible cases are not covered here, like renaming a file and the
1717
// name and path are unchanged, or including two file names for an add
1818
// line. But validation on at least file name is done.
1919
describe("#oneChange", function () {
2020
it("returns the appropriate commit message for a new file", function () {
2121
assert.strictEqual(oneChange("A\tfoo.txt"), "create foo.txt");
22-
// Maybe create foo.txt in bar, if the dir is not too long?
22+
23+
// TODO: Maybe 'create foo.txt in bar', if the dir is not too long?
2324
assert.strictEqual(oneChange("A\tbar/foo.txt"), "create foo.txt");
25+
26+
assert.strictEqual(oneChange("A\tfoo bar.txt"), "create 'foo bar.txt'");
27+
assert.strictEqual(
28+
oneChange("A\tfizz buzz/foo bar.txt"),
29+
"create 'foo bar.txt'"
30+
);
2431
});
2532

26-
it("throws an error if no filepath can be no generated", function () {
33+
it("throws an error if no file path can be no generated", function () {
2734
assert.throws(() => oneChange("A "));
2835
});
2936

@@ -58,7 +65,7 @@ describe("Generate commit message for a single changed file", function () {
5865
);
5966
});
6067

61-
it("describes a file moved out of the repo root", function () {
68+
it("describes a file moved out of the repo root to another directory", function () {
6269
assert.strictEqual(
6370
oneChange("R\tfoo.txt\tfizz/foo.txt"),
6471
"move foo.txt to fizz"
@@ -68,6 +75,16 @@ describe("Generate commit message for a single changed file", function () {
6875
oneChange("R\tfoo.txt\tfizz/buzz/foo.txt"),
6976
"move foo.txt to fizz/buzz"
7077
);
78+
79+
assert.strictEqual(
80+
oneChange("R\tfoo.txt\tfizz buzz/foo.txt"),
81+
"move foo.txt to 'fizz buzz'"
82+
);
83+
84+
assert.strictEqual(
85+
oneChange("R\tfoo bar.txt\tfizz/foo bar.txt"),
86+
"move 'foo bar.txt' to fizz"
87+
);
7188
});
7289

7390
it("describes a file moved out of a subdirectory", function () {
@@ -82,9 +99,14 @@ describe("Generate commit message for a single changed file", function () {
8299
);
83100

84101
assert.strictEqual(
85-
oneChange("R\tfizz/buzz/foo.txt\tfizz/buzz/foo.txt"),
102+
oneChange("R\tfizz/foo.txt\tfizz/buzz/foo.txt"),
86103
"move foo.txt to fizz/buzz"
87104
);
105+
106+
assert.strictEqual(
107+
oneChange("R\tfizz/foo bar.txt\tfizz/buzz baz/foo bar.txt"),
108+
"move 'foo bar.txt' to 'fizz/buzz baz'"
109+
);
88110
});
89111

90112
it("describes a file that was both moved and renamed", function () {
@@ -102,6 +124,11 @@ describe("Generate commit message for a single changed file", function () {
102124
oneChange("R\tbar/foo.txt\tfizz/fuzz.txt"),
103125
"move and rename foo.txt to fizz/fuzz.txt"
104126
);
127+
128+
assert.strictEqual(
129+
oneChange("R\tbar/foo.txt\tfizz/baz fuzz.txt"),
130+
"move and rename foo.txt to 'fizz/baz fuzz.txt'"
131+
);
105132
});
106133

107134
it("ignores percentage changed value for a file that was both moved and renamed", function () {
@@ -127,6 +154,10 @@ describe("Generate commit message for a single changed file", function () {
127154
);
128155

129156
assert.strictEqual(oneChange("A\tfoo/index.md"), "create foo/index.md");
157+
assert.strictEqual(
158+
oneChange("A\tfoo/index bazz.md"),
159+
"create 'foo/index bazz.md'"
160+
);
130161
assert.strictEqual(oneChange("A\tfoo/index.js"), "create foo/index.js");
131162
});
132163
});
@@ -143,6 +174,14 @@ describe("Generate description for a few changed files which each get named", fu
143174
"create foo.txt and bar.txt"
144175
);
145176

177+
assert.strictEqual(
178+
namedFilesDesc([
179+
{ x: "A", from: "foo bar.txt", y: " ", to: "" },
180+
{ x: "A", from: "fizz buzz.txt", y: " ", to: "" },
181+
]),
182+
"create 'foo bar.txt' and 'fizz buzz.txt'"
183+
);
184+
146185
assert.strictEqual(
147186
namedFilesDesc([
148187
{ x: "M", from: "foo.txt", y: " ", to: "" },
@@ -170,6 +209,15 @@ describe("Generate description for a few changed files which each get named", fu
170209
"create foo.txt, bar.txt and buzz.js"
171210
);
172211

212+
assert.strictEqual(
213+
namedFilesDesc([
214+
{ x: "A", from: "foo.txt", y: " ", to: "" },
215+
{ x: "A", from: "docs/bar fuzz.txt", y: " ", to: "" },
216+
{ x: "A", from: "buzz.js", y: " ", to: "" },
217+
]),
218+
"create foo.txt, 'bar fuzz.txt' and buzz.js"
219+
);
220+
173221
assert.strictEqual(
174222
namedFilesDesc([
175223
{ x: "D", from: "foo.txt", y: " ", to: "" },

0 commit comments

Comments
 (0)