gogcligog upload命令完全指南:从本地文件上传到 Google Drive 与条件式内容替换
【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcli
gog upload是 gogcli 中把本地文件上传到 Google Drive 的命令,同时是drive upload的别名(还支持up、put缩写),它把"新建上传"和"内容替换"两种场景统一到一条命令里。本文以 docs/commands/gog-upload.md 为骨架,结合 internal/cmd/drive_upload.go 的源码实现与 internal/cmd/drive_upload_replace_test.go 的测试用例,完整讲解命令用法、每个 flag 的作用与限制、MIME 类型推断与格式转换规则、frontmatter 剥离逻辑,以及基于版本号(ETag)的原子条件替换原理,读完即可在生产与脚本场景中安全使用。
命令概览
gog upload上传本地文件到 Drive,源文档将其定位为 "alias for 'drive upload'"——也就是说它挂在gog drive命令组下(见 internal/cmd/drive.go#L66 中Upload DriveUploadCmd的注册),同时作为顶层命令暴露。该文档由gog schema --json自动生成(页首注明 "Generated fromgog schema --json. Do not edit this page by hand; runmake docs-commands"),因此文档中的 flag 列表与命令行实际行为保持一致。
基本用法:
gog upload (up,put) <localPath> [flags]其中up、put是upload的别名。命令的父命令是 gog,完整命令索引见 Command index。
从源码看,上传的执行入口是DriveUploadCmd.Run(internal/cmd/drive_upload.go#L159),整体流程为:解析参数 → 打开本地文件并确定大小 → 构造 dry-run 描述 → 按需走"创建"或"替换"或"条件替换"三条分支。一个最简上传示例:
gog upload ./report.pdf # 输出示例(文本模式): # id 1abcXYZ... # name report.pdf # link https://drive.google.com/file/d/1abcXYZ.../view命令结束后会输出文件id、name以及可用的webViewLink(详见writeDriveUploadResult,internal/cmd/drive_upload.go#L422)。
Flag 完整参考
以下表格完整继承自原文档,并补充了类型、默认值与语义说明:
| Flag | 类型 | 默认值 | 说明 |
|---|---|---|---|
--access-token | string | 直接使用提供的访问令牌(绕过已存储的 refresh token;令牌约 1 小时过期) | |
-a--account--acct | string | 指定账户邮箱、别名或auto,用于已认证的 Google API 命令 | |
--client | string | OAuth client 名称(选择已存储的凭据 + token bucket) | |
--color | string | auto | 颜色输出:auto|always|never |
--convert | bool | 根据文件扩展名自动转换为 Google 原生格式(仅创建场景) | |
--convert-to | string | 转换为指定的 Google 格式:doc|sheet|slides(仅创建场景) | |
--disable-commands | string | 逗号分隔的禁用命令列表;支持点路径 | |
-n--dry-run--dryrun--noop--preview | bool | 不做实际修改,打印将要执行的操作并以成功退出 | |
--enable-commands | string | 逗号分隔的启用命令前缀列表;支持点路径(限制 CLI 范围) | |
--enable-commands-exact | string | 逗号分隔的精确启用命令列表;点路径生效且父命令不会启用子命令 | |
-y--force--assume-yes--yes | bool | 跳过破坏性命令的确认提示 | |
--gmail-no-send | bool | false | 阻止 Gmail 发送操作(Agent 安全开关) |
-h--help | kong.helpFlag | 显示上下文相关帮助 | |
--home | string | 覆盖 gogcli 的 config/data/state/cache 根目录(等价于GOG_HOME) | |
--if-version | *int64 | 仅当当前 Drive 版本匹配时才替换;使用原子前置条件,若文件被改动则报告冲突(需要--replace;冲突时重新读取并重试) | |
-j--json--machine | bool | false | 输出 JSON 到 stdout(最适合脚本化) |
--keep-frontmatter | bool | Markdown 转 Google Doc 时保留 YAML frontmatter(---)(配合--convert或--convert-to doc;默认剥离) | |
--keep-revision-forever | bool | 永久保留新的 head revision(仅二进制文件) | |
--mime-type | string | 覆盖 MIME 类型推断 | |
--name | string | 覆盖文件名(创建时)或重命名目标(替换时) | |
--no-input--non-interactive--noninteractive | bool | 从不提示;失败即报错(适合 CI) | |
--parent | string | 目标文件夹 ID(仅创建场景) | |
-p--plain--tsv | bool | false | 输出稳定的、可解析的纯文本到 stdout(TSV;无颜色) |
--quota-project | string | 用于 API 计费的 Google Cloud 项目(作为X-Goog-User-Project发送;某些 API 在使用--access-token或 ADC 时需要它) | |
--readonly | bool | false | 运行时阻止变更类 API 请求;auth add也只会请求只读 OAuth scope |
--replace | string | 替换已存在 Drive 文件 ID 的内容(保留共享链接/权限;除非设置--if-version,否则无条件替换) | |
--results-only | bool | JSON 模式下仅输出主要结果(丢弃nextPageToken等信封字段) | |
--select--pick--project | string | JSON 模式下选择逗号分隔的字段(尽力而为;支持点路径)。多数命令推荐使用--fields | |
-v--verbose | bool | 启用详细日志 | |
--version | kong.VersionFlag | 打印版本并退出 | |
--wrap-untrusted | bool | false | JSON/raw 输出中,将抓取的文本字段包裹在外部不可信内容标记中 |
其中--access-token、--client、--quota-project、--readonly、--home、--enable-commands、--no-input等属于 internal/cmd/root.go 中的全局根 flag,适用于所有命令,而下面这些 flag 是upload特有的(定义于DriveUploadCmd,internal/cmd/drive_upload.go#L25):
localPath(位置参数,必填):本地文件路径--name:创建时覆盖文件名 / 替换时重命名目标--parent:目标文件夹 ID(仅创建)--replace:要替换内容的已存在文件 ID--if-version:条件替换的前置版本号(要求--replace)--mime-type:覆盖 MIME 推断--keep-revision-forever:永久保留 head revision(仅二进制)--convert/--convert-to:Google 原生格式转换(仅创建)--keep-frontmatter:转换时保留 YAML frontmatter
上传模式一:新建文件(create)
不带--replace时,命令创建新文件。核心实现在runDriveCreateUpload(internal/cmd/drive_upload.go#L299):
meta := &drive.File{Name: opts.fileName} if opts.parent != "" { meta.Parents = []string{opts.parent} } call := svc.Files.Create(meta). SupportsAllDrives(true). Media(file, gapi.ContentType(opts.mimeType)). Fields("id, name, mimeType, size, webViewLink"). Context(ctx)要点:
- 默认文件名:未指定
--name时,使用本地文件的 base name(filepath.Base)。 - 指定父目录:通过
--parent <folderId>指定目标文件夹,否则上传到根目录。 - 支持共享盘:所有 Drive 调用都设置了
SupportsAllDrives(true),因此可以上传到 Shared Drive / 团队成员盘。 - 返回字段:
id, name, mimeType, size, webViewLink,其中webViewLink即网页查看地址。
创建示例:
# 上传到根目录 gog upload ~/backup.tar.gz # 上传到指定文件夹 gog upload report.pdf --parent 1AbC...folderId # 自定义云端文件名 gog upload report.pdf --name "2026 年度报告.pdf" # 指定 MIME 类型(覆盖推断) gog upload data.bin --mime-type application/octet-streamMIME 类型推断规则
未指定--mime-type时,guessMimeType(internal/cmd/drive_upload.go#L52)根据扩展名(小写)推断,规则如下(常量定义见 internal/cmd/drive.go#L26-L55):
| 扩展名 | MIME 类型 |
|---|---|
.pdf | application/pdf |
.doc | application/msword |
.docx | application/vnd.openxmlformats-officedocument.wordprocessingml.document |
.xls | application/vnd.ms-excel |
.xlsx | application/vnd.openxmlformats-officedocument.spreadsheetml.sheet |
.ppt | application/vnd.ms-powerpoint |
.pptx | application/vnd.openxmlformats-officedocument.presentationml.presentation |
.png | image/png |
.jpg/.jpeg | image/jpeg |
.gif | image/gif |
.txt | text/plain |
.html | text/html |
.css | text/css |
.js | application/javascript |
.json | application/json |
.zip | application/zip |
.csv | text/csv |
.md | text/markdown |
| 其他 | application/octet-stream |
--mime-type的作用是覆盖这里的推断结果,并通过gapi.ContentType(opts.mimeType)作为上传媒体类型发送。
上传模式二:替换已有文件内容(replace)
--replace <fileId>用本地文件内容替换一个已存在的 Drive 文件,其设计意图是保留原文件的 ID、共享链接与权限,而不是新建文件。实现位于runDriveReplaceUpload(internal/cmd/drive_upload.go#L327):
gog upload new-version.pdf --replace 1AbC...fileId源码要点:
- 先
Files.Get读取目标元数据(id, mimeType); - 拒绝替换 Google Workspace 原生文件:若目标 MIME 以
application/vnd.google-apps.开头(如 Google Doc/Sheet/Slides),直接报错cannot replace content for Google Workspace files——这些文件的内容不能通过上传媒体流覆盖,相关校验逻辑见 internal/cmd/drive_upload.go#L336-L338; - 通过
Files.Update更新内容,同样SupportsAllDrives(true); - 支持用
--name顺带重命名目标文件(meta.Name = opts.fileName); - 输出中额外带
replaced true字段。
替换模式的参数限制(prepareDriveUpload中的校验,internal/cmd/drive_upload.go#L234-L245):
--if-version必须与--replace一起使用,否则报--if-version requires --replace;且版本号必须为正整数;--parent不能与--replace组合(否则报--parent cannot be combined with --replace (use drive move),提示改用drive move);--convert/--convert-to不能与--replace组合。
上传模式三:条件式替换(--if-version,原子防冲突)
--if-version <n>提供基于版本的前置条件替换,解决"多人协作中覆盖了别人刚改过的内容"这一类竞态问题。核心实现在runDriveConditionalReplaceUpload(internal/cmd/drive_upload.go#L360)。
工作流程:
- 用 Drive v2 API 读取目标文件的
id, mimeType, version, etag; - 同样拒绝替换 Google Workspace 原生文件;
- fail-closed 检查:若元数据响应中没有 version 或 etag(
existing.Version <= 0或 etag 为空),直接报错并不做任何更新("no update was attempted"),见 internal/cmd/drive_upload.go#L372-L378; - 预检版本:若
existing.Version != *opts.ifVersion,报冲突conditional Drive replacement conflict ... expected version %d, current version is %d; re-read the file and reapply your edit,不发请求; - 原子更新:通过
Files.Update+If-Match: <etag>头发送,服务端若发现文件已变化会返回 412 Precondition Failed,代码将其映射为用户可读的冲突错误(internal/cmd/drive_upload.go#L400-L411)。
原子性保障(源码中的关键注释,internal/cmd/drive_upload.go#L387-L390):
// Conditional replacement must be a single atomic attempt. ChunkSize(0) // prevents the generated client from switching large media to its internally // retrying resumable uploader, while the request context disables retries in // gog's authenticated RetryTransport. updateCtx := gogapi.WithoutRetries(ctx) call := svc.Files.Update(opts.replaceFileID, meta). SupportsAllDrives(true). Media(file, gapi.ContentType(opts.mimeType), gapi.ChunkSize(0)). Fields("id, title, mimeType, fileSize, alternateLink"). Context(updateCtx)即:ChunkSize(0)阻止大文件媒体走会自动重试的 resumable uploader,WithoutRetries关闭重试传输层,配合If-Match保证"一次尝试、要么成功要么冲突",绝不会有后台重试造成二次覆盖。--keep-revision-forever在该分支映射为 v2 的Pinned(true)。
该行为的测试覆盖非常完整(internal/cmd/drive_upload_replace_test.go):
TestDriveUpload_ConditionalReplace_MatchingVersionUsesETag(L421):版本匹配时使用 ETag 头执行替换;TestDriveUpload_ConditionalReplace_StaleVersionStopsBeforePatch(L467):版本过期时在发起更新前就停止;TestDriveUpload_ConditionalReplace_PreconditionFailureIsConflict(L503):412 被识别为冲突;TestDriveUpload_ConditionalReplace_RetryableFailureIsNotRetried(L557):可重试错误也不会被自动重试;TestDriveUpload_ConditionalReplace_LargeMediaUsesSingleRequest(L621):大媒体仍走单请求;TestDriveUpload_ConditionalReplace_MissingPreconditionMaterialFailsClosed(L677):缺少 version/etag 时 fail-closed。
典型用法是"先读版本,再条件写入"的乐观锁模式:先gog drive get <fileId>拿到当前版本号,编辑后执行:
gog upload edited.md --replace 1AbC...fileId --if-version 42若返回冲突,说明自读取版本号后文件已被他人修改,需要重新读取最新内容后再决定是否重试。
格式转换:--convert 与 --convert-to
仅创建场景(不能与--replace组合)支持把 Office/文本格式转换为 Google 原生格式。转换目标 MIME 常量定义见 internal/cmd/drive.go#L29-L31:
- Google Doc:
application/vnd.google-apps.document - Google Sheet:
application/vnd.google-apps.spreadsheet - Google Slides:
application/vnd.google-apps.presentation
--convert-to doc|sheet|slides:显式指定目标格式(googleConvertTargetMimeType,internal/cmd/drive_upload.go#L114);非法值报--convert-to: invalid value %q (use doc|sheet|slides)。
--convert:按扩展名自动判断目标格式(googleConvertMimeType,internal/cmd/drive_upload.go#L98):
| 本地扩展名 | 转换结果 |
|---|---|
.docx、.doc | Google Doc |
.xlsx、.xls、.csv | Google Sheet |
.pptx、.ppt | Google Slides |
.txt、.html、.md | Google Doc |
不支持的扩展名报--convert: unsupported file type %q (supported: docx, xlsx, pptx, doc, xls, ppt, csv, txt, html, md)。
转换时的文件名处理(stripOfficeExt与driveUploadRemoteName,internal/cmd/drive_upload.go#L149、L262):自动转换时若未显式指定--name,会去掉 Office 扩展名,使云端文件名更干净,例如report.docx→report(Google Doc);显式指定了--name则保留原名。
# 按扩展名自动转换:docx → Google Doc gog upload spec.docx --convert # 显式转换:csv → Google Sheet gog upload data.csv --convert-to sheet # 保留显式名称 gog upload spec.docx --convert --name "产品规格书"Markdown 与 YAML frontmatter 处理
当上传 Markdown(text/markdown)并触发转换(--convert或--convert-to doc)时,默认会剥离文件头部的 YAML frontmatter(---包裹的块),因为 frontmatter 对 Google Doc 是噪音。判断条件见driveUploadShouldStripMarkdownFrontmatter(internal/cmd/drive_upload.go#L269):
return !keepFrontmatter && opts.convert && opts.mimeType == mimeTextMarkdown剥离实现在stripYAMLFrontmatter(internal/cmd/drive_markdown_frontmatter.go#L13):允许文件以 UTF-8 BOM 开头,第一行 trim 后必须等于---,随后找到下一个 trim 后为---的行即视为闭合;找不到闭合分隔符则原样返回(不剥离)。剥离后文件大小按剥离结果重新计算(见openDriveUploadMedia,internal/cmd/drive_upload.go#L273)。
如需保留 frontmatter,加--keep-frontmatter:
gog upload post.md --convert --keep-frontmatter输出格式与脚本化使用
命令支持三种输出形态(由writeDriveUploadResult处理,internal/cmd/drive_upload.go#L422):
1. 默认文本输出(id/name/link键值对):
id 1AbC...fileId name report.pdf link https://drive.google.com/file/d/1AbC.../view替换场景额外输出replaced true,并可用preservedFileId确认 ID 是否保留。
2. 纯文本 TSV 输出(-p/--plain/--tsv):稳定可解析、无颜色,适合 shell 管道。
3. JSON 输出(-j/--json/--machine):结构化数据,最适合脚本:
gog upload report.pdf -j # {"file": {"id": "...", "name": "...", "mimeType": "...", "size": "...", "webViewLink": "..."}}替换场景 JSON 中还会出现"replaced": true与"preservedFileId": true/false。可配合--results-only只保留主结果、--select选择字段(点路径),详见上文的全局 flag 说明。
CI / 无人值守:--no-input保证任何需要交互提示的场景直接失败而非挂起;--dry-run(别名-n/--dryrun/--noop/--preview)打印将要执行的操作描述(含 path、name、parent、replace_file_id、mime_type、size、convert、convert_mime_type、keep_revision_forever、if_version 等字段)而不实际写入,构造逻辑见 internal/cmd/drive_upload.go#L172-L188。
gog upload big.tar.gz --dry-run # 只预览 gog upload big.tar.gz --no-input -j # CI 中失败即报错参数校验与常见错误速查
所有校验集中在prepareDriveUpload(internal/cmd/drive_upload.go#L209),可据此快速排查报错:
| 报错信息 | 含义与处理 |
|---|---|
empty localPath | 位置参数为空,必须提供本地文件路径 |
--if-version requires --replace | 条件替换必须同时指定--replace |
--if-version must be a positive integer | 版本号必须为正整数 |
--parent cannot be combined with --replace (use drive move) | 替换时不支持改父目录,改目录请用drive move |
--convert/--convert-to cannot be combined with --replace | 转换仅限创建场景 |
--convert-to: invalid value %q (use doc\|sheet\|slides) | 转换目标只能取doc/sheet/slides |
--convert: unsupported file type %q (...) | 扩展名不在可转换列表中 |
cannot replace content for Google Workspace files (mimeType=...) | 不允许用媒体流覆盖 Google 原生文档 |
conditional Drive replacement conflict ... expected version N, current version M | 版本不匹配,需重新读取后重试 |
metadata response did not include a version/ETag; no update was attempted | 元数据缺失导致 fail-closed,未执行任何更新 |
另外,本地路径支持config.ExpandPath展开(~等),--access-token的令牌约 1 小时过期,长时间任务应优先使用存储的凭据或--client指定 OAuth client。
测试与实现验证
上传相关的行为有大量自动化测试保障(均在 internal/cmd/drive_upload_replace_test.go):
- 替换场景:
TestDriveUpload_Replace_JSON(L25)、TestDriveUpload_Replace_Text(L107)验证 JSON/文本输出;TestDriveUpload_Replace_ParentValidation(L169)验证--parent与--replace互斥;TestDriveUpload_Replace_GoogleWorkspaceUnsupported(L192)验证拒绝覆盖 Google 原生文件;TestDriveUpload_Replace_ConvertValidation(L238)验证转换限制;TestDriveUpload_Replace_KeepRevisionForeverAndMimeType(L261)验证--keep-revision-forever与 MIME 覆盖; - 条件替换:前述 6 个
ConditionalReplace_*测试覆盖版本匹配、过期版本、412 冲突、禁止重试、大媒体单请求、fail-closed; - dry-run:
TestDriveUpload_IfVersionValidationAndDryRun(L331)同时覆盖--if-version的入参校验与 dry-run 描述输出; - 创建场景:
TestDriveUpload_Create_KeepRevisionForever(L763)验证创建时保留 revision。
除单测外,gog upload也被纳入端到端 dry-run 测试(internal/cmd/dryrun_e2e_test.go)与更多命令校验测试(internal/cmd/drive_validation_more_test.go),读者可以以此为模板扩展自己的脚本。
小结
gog upload用一条命令统一了 Drive 的"新建上传"与"内容替换"两类操作:
- 创建:
gog upload <localPath> [--parent <id>] [--name <n>] [--mime-type <m>],支持--convert/--convert-to转为 Google 原生格式,并自动剥离 Markdown frontmatter(可用--keep-frontmatter保留); - 无条件替换:
gog upload <localPath> --replace <fileId>,保留文件 ID 与共享权限,但不能用于 Google 原生文件; - 条件替换:
--replace <fileId> --if-version <n>,基于 version/ETag 的原子更新,适合乐观锁协作场景; - 脚本化:
-j输出 JSON、-p输出 TSV、--no-input失败即报错、--dry-run安全预览。
深入阅读推荐:命令参考 docs/commands/gog-upload.md、父命令 gog、核心实现 internal/cmd/drive_upload.go、frontmatter 剥离 internal/cmd/drive_markdown_frontmatter.go、条件替换测试 internal/cmd/drive_upload_replace_test.go。
【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcli
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考