Skip to content
This repository was archived by the owner on Mar 3, 2026. It is now read-only.

Commit c77eaa3

Browse files
authored
feat: add File#rename (#1311)
1 parent e826a52 commit c77eaa3

5 files changed

Lines changed: 236 additions & 0 deletions

File tree

samples/renameFile.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2020 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
/**
16+
* This application demonstrates how to perform basic operations on files with
17+
* the Google Cloud Storage API.
18+
*
19+
* For more information, see the README.md under /storage and the documentation
20+
* at https://cloud.google.com/storage/docs.
21+
*/
22+
23+
function main(
24+
bucketName = 'my-bucket',
25+
srcFilename = 'test2.txt',
26+
destFilename = 'test4.txt'
27+
) {
28+
// [START storage_rename_file]
29+
/**
30+
* TODO(developer): Uncomment the following lines before running the sample.
31+
*/
32+
// const bucketName = 'Name of a bucket, e.g. my-bucket';
33+
// const srcFilename = 'File to rename, e.g. file.txt';
34+
// const destFilename = 'Destination for file, e.g. renamed.txt';
35+
36+
// Imports the Google Cloud client library
37+
const {Storage} = require('@google-cloud/storage');
38+
39+
// Creates a client
40+
const storage = new Storage();
41+
42+
async function renameFile() {
43+
// renames the file
44+
await storage.bucket(bucketName).file(srcFilename).rename(destFilename);
45+
46+
console.log(
47+
`gs://${bucketName}/${srcFilename} renamed to gs://${bucketName}/${destFilename}.`
48+
);
49+
}
50+
51+
renameFile().catch(console.error);
52+
// [END storage_rename_file]
53+
}
54+
main(...process.argv.slice(2));

samples/system-test/files.test.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const bucket = storage.bucket(bucketName);
3333
const fileName = 'test.txt';
3434
const movedFileName = 'test2.txt';
3535
const copiedFileName = 'test3.txt';
36+
const renamedFileName = 'test4.txt';
3637
const signedFileName = 'signed-upload.txt';
3738
const kmsKeyName = process.env.GOOGLE_CLOUD_KMS_KEY_US;
3839
const filePath = path.join(cwd, 'resources', fileName);
@@ -178,6 +179,23 @@ describe('file', () => {
178179
assert.notMatch(output, new RegExp(copiedFileName));
179180
});
180181

182+
it('should rename a file', async () => {
183+
const output = execSync(
184+
`node renameFile.js ${bucketName} ${movedFileName} ${renamedFileName}`
185+
);
186+
assert.match(
187+
output,
188+
new RegExp(
189+
`gs://${bucketName}/${movedFileName} renamed to gs://${bucketName}/${renamedFileName}.`
190+
)
191+
);
192+
const [exists] = await bucket.file(renamedFileName).exists();
193+
assert.strictEqual(exists, true);
194+
195+
const [oldFileExists] = await bucket.file(movedFileName).exists();
196+
assert.strictEqual(oldFileExists, false);
197+
});
198+
181199
it('should make a file public', () => {
182200
const output = execSync(
183201
`node makePublic.js ${bucketName} ${copiedFileName}`

src/file.ts

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,10 @@ export interface MoveOptions {
249249
userProject?: string;
250250
}
251251

252+
export type RenameOptions = MoveOptions;
253+
export type RenameResponse = MoveResponse;
254+
export type RenameCallback = MoveCallback;
255+
252256
export type RotateEncryptionKeyOptions = string | Buffer | EncryptionKeyOptions;
253257

254258
export interface EncryptionKeyOptions {
@@ -3233,6 +3237,114 @@ class File extends ServiceObject<File> {
32333237
});
32343238
}
32353239

3240+
rename(
3241+
destinationFile: string | File,
3242+
options?: RenameOptions
3243+
): Promise<RenameResponse>;
3244+
rename(destinationFile: string | File, callback: RenameCallback): void;
3245+
rename(
3246+
destinationFile: string | File,
3247+
options: RenameOptions,
3248+
callback: RenameCallback
3249+
): void;
3250+
/**
3251+
* @typedef {array} RenameResponse
3252+
* @property {File} 0 The destination File.
3253+
* @property {object} 1 The full API response.
3254+
*/
3255+
/**
3256+
* @callback RenameCallback
3257+
* @param {?Error} err Request error, if any.
3258+
* @param {?File} destinationFile The destination File.
3259+
* @param {object} apiResponse The full API response.
3260+
*/
3261+
/**
3262+
* @typedef {object} RenameOptions Configuration options for File#move(). See an
3263+
* [Object
3264+
* resource](https://cloud.google.com/storage/docs/json_api/v1/objects#resource).
3265+
* @param {string} [userProject] The ID of the project which will be
3266+
* billed for the request.
3267+
*/
3268+
/**
3269+
* Rename this file.
3270+
*
3271+
* **Warning**:
3272+
* There is currently no atomic `rename` method in the Cloud Storage API,
3273+
* so this method is an alias of {@link File#move}, which in turn is a
3274+
* composition of {@link File#copy} (to the new location) and
3275+
* {@link File#delete} (from the old location). While
3276+
* unlikely, it is possible that an error returned to your callback could be
3277+
* triggered from either one of these API calls failing, which could leave a
3278+
* duplicate file lingering. The error message will indicate what operation
3279+
* has failed.
3280+
*
3281+
* @param {string|File} destinationFile Destination file.
3282+
* @param {RenameCallback} [callback] Callback function.
3283+
* @returns {Promise<RenameResponse>}
3284+
*
3285+
* @example
3286+
* const {Storage} = require('@google-cloud/storage');
3287+
* const storage = new Storage();
3288+
*
3289+
* //-
3290+
* // You can pass in a string or a File object.
3291+
* //
3292+
* // For all of the below examples, assume we are working with the following
3293+
* // Bucket and File objects.
3294+
* //-
3295+
*
3296+
* const bucket = storage.bucket('my-bucket');
3297+
* const file = bucket.file('my-image.png');
3298+
*
3299+
* //-
3300+
* // You can pass in a string for the destinationFile.
3301+
* //-
3302+
* file.rename('renamed-image.png', function(err, renamedFile, apiResponse) {
3303+
* // `my-bucket` no longer contains:
3304+
* // - "my-image.png"
3305+
* // but contains instead:
3306+
* // - "renamed-image.png"
3307+
*
3308+
* // `renamedFile` is an instance of a File object that refers to your
3309+
* // renamed file.
3310+
* });
3311+
*
3312+
* //-
3313+
* // You can pass in a File object.
3314+
* //-
3315+
* const anotherFile = anotherBucket.file('my-awesome-image.png');
3316+
*
3317+
* file.rename(anotherFile, function(err, renamedFile, apiResponse) {
3318+
* // `my-bucket` no longer contains:
3319+
* // - "my-image.png"
3320+
*
3321+
* // Note:
3322+
* // The `renamedFile` parameter is equal to `anotherFile`.
3323+
* });
3324+
*
3325+
* //-
3326+
* // If the callback is omitted, we'll return a Promise.
3327+
* //-
3328+
* file.rename('my-renamed-image.png').then(function(data) {
3329+
* const renamedFile = data[0];
3330+
* const apiResponse = data[1];
3331+
* });
3332+
*/
3333+
rename(
3334+
destinationFile: string | File,
3335+
optionsOrCallback?: RenameOptions | RenameCallback,
3336+
callback?: RenameCallback
3337+
): Promise<RenameResponse> | void {
3338+
const options =
3339+
typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
3340+
callback =
3341+
typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
3342+
3343+
callback = callback || util.noop;
3344+
3345+
this.move(destinationFile, options, callback);
3346+
}
3347+
32363348
request(reqOpts: DecorateRequestOptions): Promise<[ResponseBody, Metadata]>;
32373349
request(
32383350
reqOpts: DecorateRequestOptions,

system-test/storage.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,6 +1950,23 @@ describe('storage', () => {
19501950
})
19511951
);
19521952

1953+
it(
1954+
'file#rename',
1955+
doubleTest((options: GetFileOptions, done: SaveCallback) => {
1956+
const newFile = bucketNonAllowList.file(generateName());
1957+
1958+
file.rename(newFile, options, err => {
1959+
if (err) {
1960+
done(err);
1961+
return;
1962+
}
1963+
1964+
// Re-create the file. The tests need it.
1965+
file.save('newcontent', options, done);
1966+
});
1967+
})
1968+
);
1969+
19531970
it(
19541971
'file#setMetadata',
19551972
doubleTest(

test/file.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
SignedPostPolicyV4Output,
5252
GenerateSignedPostPolicyV4Options,
5353
STORAGE_POST_POLICY_BASE_URL,
54+
MoveOptions,
5455
} from '../src/file';
5556

5657
let promisified = false;
@@ -3679,6 +3680,40 @@ describe('File', () => {
36793680
});
36803681
});
36813682

3683+
describe('rename', () => {
3684+
it('should correctly call File#move', done => {
3685+
const newFileName = 'renamed-file.txt';
3686+
const options = {};
3687+
file.move = (dest: string, opts: MoveOptions, cb: Function) => {
3688+
assert.strictEqual(dest, newFileName);
3689+
assert.strictEqual(opts, options);
3690+
assert.strictEqual(cb, done);
3691+
cb();
3692+
};
3693+
file.rename(newFileName, options, done);
3694+
});
3695+
3696+
it('should accept File object', done => {
3697+
const newFileObject = new File(BUCKET, 'renamed-file.txt');
3698+
const options = {};
3699+
file.move = (dest: string, opts: MoveOptions, cb: Function) => {
3700+
assert.strictEqual(dest, newFileObject);
3701+
assert.strictEqual(opts, options);
3702+
assert.strictEqual(cb, done);
3703+
cb();
3704+
};
3705+
file.rename(newFileObject, options, done);
3706+
});
3707+
3708+
it('should not require options', done => {
3709+
file.move = (dest: string, opts: MoveOptions, cb: Function) => {
3710+
assert.deepStrictEqual(opts, {});
3711+
cb();
3712+
};
3713+
file.rename('new-name', done);
3714+
});
3715+
});
3716+
36823717
describe('request', () => {
36833718
it('should call the parent request function', () => {
36843719
const options = {};

0 commit comments

Comments
 (0)