Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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