Skip to content
Open
2 changes: 1 addition & 1 deletion dev-test/backends/test/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -253,4 +253,4 @@ collections: # A list of collections the CMS should be able to edit
- label: Title
name: title
widget: string
meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
meta: { path: { widget: string, label: 'Path' } }
2 changes: 1 addition & 1 deletion dev-test/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,4 @@ collections: # A list of collections the CMS should be able to edit
- label: Title
name: title
widget: string
meta: { path: { widget: string, label: 'Path', index_file: 'index' } }
meta: { path: { widget: string, label: 'Path' } }
46 changes: 28 additions & 18 deletions packages/decap-cms-backend-azure/src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import {
readFileMetadata,
branchFromContentKey,
} from 'decap-cms-lib-util';
import { dirname, basename } from 'path';
import { basename, dirname } from 'path';

import type { ApiRequest, AssetProxy, PersistOptions, DataFile } from 'decap-cms-lib-util';
import type { Map } from 'immutable';
Expand Down Expand Up @@ -503,7 +503,11 @@ export default class API {
}));
}

async getCommitItems(files: { path: string; newPath?: string }[], branch: string) {
async getCommitItems(
files: { path: string; newPath?: string }[],
branch: string,
subfolders = true,
) {
const items = await Promise.all(
files.map(async file => {
const [base64Content, fileExists] = await Promise.all([
Expand All @@ -526,32 +530,37 @@ export default class API {
}),
);

// move children
for (const item of items.filter(i => i.oldPath && i.action === AzureCommitChangeType.RENAME)) {
const sourceDir = dirname(item.oldPath as string);
const destDir = dirname(item.path);
const children = await this.listFiles(sourceDir, true, branch);
children
.filter(file => file.path !== item.oldPath)
.forEach(file => {
items.push({
action: AzureCommitChangeType.RENAME,
path: file.path.replace(sourceDir, destDir),
oldPath: file.path,
// move children when subfolders is true (legacy/default behavior)
if (subfolders) {
for (const item of items.filter(
i => i.oldPath && i.action === AzureCommitChangeType.RENAME,
)) {
const sourceDir = dirname(item.oldPath as string);
const destDir = dirname(item.path);
const children = await this.listFiles(sourceDir, true, branch);
children
.filter(file => file.path !== item.oldPath)
.forEach(file => {
items.push({
action: AzureCommitChangeType.RENAME,
path: file.path.replace(sourceDir, destDir),
oldPath: file.path,
});
});
});
}
}

return items;
}

async persistFiles(dataFiles: DataFile[], mediaFiles: AssetProxy[], options: PersistOptions) {
const files = [...dataFiles, ...mediaFiles];
const subfolders = options.hasSubfolders !== false; // default to true
if (options.useWorkflow) {
const slug = dataFiles[0].slug;
return this.editorialWorkflowGit(files, slug, options);
} else {
const items = await this.getCommitItems(files, this.branch);
const items = await this.getCommitItems(files, this.branch, subfolders);

return this.uploadAndCommit(items, options.commitMessage, this.branch, true);
}
Expand Down Expand Up @@ -677,9 +686,10 @@ export default class API {
const contentKey = generateContentKey(options.collectionName as string, slug);
const branch = branchFromContentKey(contentKey);
const unpublished = options.unpublished || false;
const subfolders = options.hasSubfolders !== false; // default to true

if (!unpublished) {
const items = await this.getCommitItems(files, this.branch);
const items = await this.getCommitItems(files, this.branch, subfolders);

await this.uploadAndCommit(items, options.commitMessage, branch, true);
await this.createPullRequest(
Expand All @@ -688,7 +698,7 @@ export default class API {
options.status || this.initialWorkflowStatus,
);
} else {
const items = await this.getCommitItems(files, branch);
const items = await this.getCommitItems(files, branch, subfolders);
await this.uploadAndCommit(items, options.commitMessage, branch, false);
}
}
Expand Down
66 changes: 46 additions & 20 deletions packages/decap-cms-backend-bitbucket/src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import {
readFileMetadata,
throwOnConflictingBranches,
} from 'decap-cms-lib-util';
import { dirname } from 'path';
import { oneLine } from 'common-tags';
import { parse } from 'what-the-diff';
import { dirname } from 'path';

import type {
ApiRequest,
Expand Down Expand Up @@ -432,45 +432,63 @@ export default class API {
commitMessage,
branch,
parentSha,
}: { commitMessage: string; branch: string; parentSha?: string },
hasSubfolders = true,
}: { commitMessage: string; branch: string; parentSha?: string; hasSubfolders?: boolean },
) {
const formData = new FormData();
const toMove: { from: string; to: string; contentBlob: Blob }[] = [];
const toMove: { from: string; to: string; contentBlob: Blob; hasSubfolders: boolean }[] = [];
files.forEach(file => {
if (file.delete) {
// delete the file
formData.append('files', file.path);
} else if (file.newPath) {
const contentBlob = get(file, 'fileObj', new Blob([(file as DataFile).raw]));
toMove.push({ from: file.path, to: file.newPath, contentBlob });
toMove.push({ from: file.path, to: file.newPath, contentBlob, hasSubfolders });
} else {
// add/modify the file
const contentBlob = get(file, 'fileObj', new Blob([(file as DataFile).raw]));
// Third param is filename header, in case path is `message`, `branch`, etc.
formData.append(file.path, contentBlob, basename(file.path));
}
});
for (const { from, to, contentBlob } of toMove) {
const sourceDir = dirname(from);
const destDir = dirname(to);
const filesBranch = parentSha ? this.branch : branch;
const files = await this.listAllFiles(sourceDir, 100, filesBranch);
for (const file of files) {
for (const { from, to, contentBlob, hasSubfolders } of toMove) {
if (!hasSubfolders) {
// New behavior (subfolders: false): Only move the specific file
// to move a file in Bitbucket we need to delete the old path
// and upload the file content to the new path
// NOTE: this is very wasteful, and also the Bitbucket `diff` API
// reports these files as deleted+added instead of renamed
// delete current path
formData.append('files', file.path);
formData.append('files', from);
// create in new path
const content =
file.path === from
? contentBlob
: await this.readFile(file.path, null, {
branch: filesBranch,
parseText: false,
});
formData.append(file.path.replace(sourceDir, destDir), content, basename(file.path));
formData.append(to, contentBlob, basename(to));
} else {
// Legacy behavior (subfolders: true, default): Move all files in the directory
const sourceDir = dirname(from);
const destDir = dirname(to);
const filesBranch = parentSha ? this.branch : branch;
const files = await this.listAllFiles(sourceDir, 100, filesBranch);
for (const file of files) {
// to move a file in Bitbucket we need to delete the old path
// and upload the file content to the new path
// NOTE: this is very wasteful, and also the Bitbucket `diff` API
// reports these files as deleted+added instead of renamed
// delete current path
formData.append('files', file.path);
// create in new path
const content =
file.path === from
? contentBlob
: await this.readFile(file.path, null, {
branch: filesBranch,
parseText: false,
});
formData.append(
file.path.replace(sourceDir, destDir),
content as Blob,
basename(file.path),
);
}
}
}

Expand Down Expand Up @@ -508,11 +526,16 @@ export default class API {

async persistFiles(dataFiles: DataFile[], mediaFiles: AssetProxy[], options: PersistOptions) {
const files = [...dataFiles, ...mediaFiles];
const hasSubfolders = options.hasSubfolders !== false; // default to true
if (options.useWorkflow) {
const slug = dataFiles[0].slug;
return this.editorialWorkflowGit(files, slug, options);
} else {
return this.uploadFiles(files, { commitMessage: options.commitMessage, branch: this.branch });
return this.uploadFiles(files, {
commitMessage: options.commitMessage,
branch: this.branch,
hasSubfolders,
});
}
}

Expand Down Expand Up @@ -599,12 +622,14 @@ export default class API {
const contentKey = generateContentKey(options.collectionName as string, slug);
const branch = branchFromContentKey(contentKey);
const unpublished = options.unpublished || false;
const hasSubfolders = options.hasSubfolders !== false; // default to true
if (!unpublished) {
const defaultBranchSha = await this.branchCommitSha(this.branch);
await this.uploadFiles(files, {
commitMessage: options.commitMessage,
branch,
parentSha: defaultBranchSha,
hasSubfolders,
});
await this.createPullRequest(
branch,
Expand All @@ -624,6 +649,7 @@ export default class API {
await this.uploadFiles([...files, ...toDelete], {
commitMessage: options.commitMessage,
branch,
hasSubfolders,
});
}
}
Expand Down
51 changes: 39 additions & 12 deletions packages/decap-cms-backend-github/src/API.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import result from 'lodash/result';
import trimStart from 'lodash/trimStart';
import trim from 'lodash/trim';
import { oneLine } from 'common-tags';
import { dirname } from 'path';
import {
getAllResponses,
APIError,
Expand All @@ -29,7 +30,6 @@ import {
unsentRequest,
throwOnConflictingBranches,
} from 'decap-cms-lib-util';
import { dirname } from 'path';

import type {
AssetProxy,
Expand Down Expand Up @@ -1011,9 +1011,15 @@ export default class API {
const contentKey = this.generateContentKey(options.collectionName as string, slug);
const branch = branchFromContentKey(contentKey);
const unpublished = options.unpublished || false;
const hasSubfolders = options.hasSubfolders !== false; // default to true
if (!unpublished) {
const branchData = await this.getDefaultBranch();
const changeTree = await this.updateTree(branchData.commit.sha, files);
const changeTree = await this.updateTree(
branchData.commit.sha,
files,
this.branch,
hasSubfolders,
);
const commitResponse = await this.commit(options.commitMessage, changeTree);

if (this.useOpenAuthoring) {
Expand Down Expand Up @@ -1048,7 +1054,7 @@ export default class API {
// rebase the branch before applying new changes
const rebasedHead = await this.rebaseBranch(branch);
const treeFiles = mediaFilesToRemove.concat(files);
const changeTree = await this.updateTree(rebasedHead.sha, treeFiles, branch);
const changeTree = await this.updateTree(rebasedHead.sha, treeFiles, branch, hasSubfolders);
const commit = await this.commit(options.commitMessage, changeTree);

return this.patchBranch(branch, commit.sha, { force: true });
Expand Down Expand Up @@ -1418,6 +1424,7 @@ export default class API {
baseSha: string,
files: { path: string; sha: string | null; newPath?: string }[],
branch = this.branch,
hasSubfolders = true,
) {
const toMove: { from: string; to: string; sha: string }[] = [];
const tree = files.reduce((acc, file) => {
Expand All @@ -1438,24 +1445,44 @@ export default class API {
}, [] as TreeEntry[]);

for (const { from, to, sha } of toMove) {
const sourceDir = dirname(from);
const destDir = dirname(to);
const files = await this.listFiles(sourceDir, { branch, depth: 100 });
for (const file of files) {
// delete current path
if (!hasSubfolders) {
// New behavior (subfolders: false): Only move the specific file
// Delete the file at the old path
tree.push({
path: file.path,
path: trimStart(from, '/'),
mode: '100644',
type: 'blob',
sha: null,
});
// create in new path
// Create the file at the new path
tree.push({
path: file.path.replace(sourceDir, destDir),
path: trimStart(to, '/'),
mode: '100644',
type: 'blob',
sha: file.path === from ? sha : file.id,
sha,
});
} else {
// Legacy behavior (subfolders: true, default): Move all files in the directory
// This is for collections where all files in a folder represent a single entry
const sourceDir = dirname(from);
const destDir = dirname(to);
const files = await this.listFiles(sourceDir, { branch, depth: 100 });
for (const file of files) {
// delete current path
tree.push({
path: file.path,
mode: '100644',
type: 'blob',
sha: null,
});
// create in new path
tree.push({
path: file.path.replace(sourceDir, destDir),
mode: '100644',
type: 'blob',
sha: file.path === from ? sha : file.id,
});
}
}
}

Expand Down
Loading
Loading