news 2026/9/18 9:17:54

gogcli `gog upload` 命令完全指南:从本地文件上传到 Google Drive 与条件式内容替换

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
gogcli `gog upload` 命令完全指南:从本地文件上传到 Google Drive 与条件式内容替换

gogcligog upload命令完全指南:从本地文件上传到 Google Drive 与条件式内容替换

【免费下载链接】gogcliGoogle Workspace in your terminal.项目地址: https://gitcode.com/GitHub_Trending/gogcl/gogcli

gog upload是 gogcli 中把本地文件上传到 Google Drive 的命令,同时是drive upload的别名(还支持upput缩写),它把"新建上传"和"内容替换"两种场景统一到一条命令里。本文以 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]

其中upputupload的别名。命令的父命令是 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

命令结束后会输出文件idname以及可用的webViewLink(详见writeDriveUploadResult,internal/cmd/drive_upload.go#L422)。

Flag 完整参考

以下表格完整继承自原文档,并补充了类型、默认值与语义说明:

Flag类型默认值说明
--access-tokenstring直接使用提供的访问令牌(绕过已存储的 refresh token;令牌约 1 小时过期)
-a
--account
--acct
string指定账户邮箱、别名或auto,用于已认证的 Google API 命令
--clientstringOAuth client 名称(选择已存储的凭据 + token bucket)
--colorstringauto颜色输出:auto|always|never
--convertbool根据文件扩展名自动转换为 Google 原生格式(仅创建场景)
--convert-tostring转换为指定的 Google 格式:doc|sheet|slides(仅创建场景)
--disable-commandsstring逗号分隔的禁用命令列表;支持点路径
-n
--dry-run
--dryrun
--noop
--preview
bool不做实际修改,打印将要执行的操作并以成功退出
--enable-commandsstring逗号分隔的启用命令前缀列表;支持点路径(限制 CLI 范围)
--enable-commands-exactstring逗号分隔的精确启用命令列表;点路径生效且父命令不会启用子命令
-y
--force
--assume-yes
--yes
bool跳过破坏性命令的确认提示
--gmail-no-sendboolfalse阻止 Gmail 发送操作(Agent 安全开关)
-h
--help
kong.helpFlag显示上下文相关帮助
--homestring覆盖 gogcli 的 config/data/state/cache 根目录(等价于GOG_HOME
--if-version*int64仅当当前 Drive 版本匹配时才替换;使用原子前置条件,若文件被改动则报告冲突(需要--replace;冲突时重新读取并重试)
-j
--json
--machine
boolfalse输出 JSON 到 stdout(最适合脚本化)
--keep-frontmatterboolMarkdown 转 Google Doc 时保留 YAML frontmatter(---)(配合--convert--convert-to doc;默认剥离)
--keep-revision-foreverbool永久保留新的 head revision(仅二进制文件)
--mime-typestring覆盖 MIME 类型推断
--namestring覆盖文件名(创建时)或重命名目标(替换时)
--no-input
--non-interactive
--noninteractive
bool从不提示;失败即报错(适合 CI)
--parentstring目标文件夹 ID(仅创建场景)
-p
--plain
--tsv
boolfalse输出稳定的、可解析的纯文本到 stdout(TSV;无颜色)
--quota-projectstring用于 API 计费的 Google Cloud 项目(作为X-Goog-User-Project发送;某些 API 在使用--access-token或 ADC 时需要它)
--readonlyboolfalse运行时阻止变更类 API 请求;auth add也只会请求只读 OAuth scope
--replacestring替换已存在 Drive 文件 ID 的内容(保留共享链接/权限;除非设置--if-version,否则无条件替换)
--results-onlyboolJSON 模式下仅输出主要结果(丢弃nextPageToken等信封字段)
--select
--pick
--project
stringJSON 模式下选择逗号分隔的字段(尽力而为;支持点路径)。多数命令推荐使用--fields
-v
--verbose
bool启用详细日志
--versionkong.VersionFlag打印版本并退出
--wrap-untrustedboolfalseJSON/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-stream

MIME 类型推断规则

未指定--mime-type时,guessMimeType(internal/cmd/drive_upload.go#L52)根据扩展名(小写)推断,规则如下(常量定义见 internal/cmd/drive.go#L26-L55):

扩展名MIME 类型
.pdfapplication/pdf
.docapplication/msword
.docxapplication/vnd.openxmlformats-officedocument.wordprocessingml.document
.xlsapplication/vnd.ms-excel
.xlsxapplication/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.pptapplication/vnd.ms-powerpoint
.pptxapplication/vnd.openxmlformats-officedocument.presentationml.presentation
.pngimage/png
.jpg/.jpegimage/jpeg
.gifimage/gif
.txttext/plain
.htmltext/html
.csstext/css
.jsapplication/javascript
.jsonapplication/json
.zipapplication/zip
.csvtext/csv
.mdtext/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)。

工作流程:

  1. 用 Drive v2 API 读取目标文件的id, mimeType, version, etag
  2. 同样拒绝替换 Google Workspace 原生文件;
  3. fail-closed 检查:若元数据响应中没有 version 或 etag(existing.Version <= 0或 etag 为空),直接报错并不做任何更新("no update was attempted"),见 internal/cmd/drive_upload.go#L372-L378;
  4. 预检版本:若existing.Version != *opts.ifVersion,报冲突conditional Drive replacement conflict ... expected version %d, current version is %d; re-read the file and reapply your edit,不发请求;
  5. 原子更新:通过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.docGoogle Doc
.xlsx.xls.csvGoogle Sheet
.pptx.pptGoogle Slides
.txt.html.mdGoogle Doc

不支持的扩展名报--convert: unsupported file type %q (supported: docx, xlsx, pptx, doc, xls, ppt, csv, txt, html, md)

转换时的文件名处理stripOfficeExtdriveUploadRemoteName,internal/cmd/drive_upload.go#L149、L262):自动转换时若未显式指定--name,会去掉 Office 扩展名,使云端文件名更干净,例如report.docxreport(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-runTestDriveUpload_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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 9:17:24

泄露的 API key 被模型使用,TaoToken Key 如何隔离调用

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:14:14

Conda求解失败?一文读懂frozen/flexible solve及依赖冲突排查修复

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:14:12

解放重卡后钢板弹簧吊耳结构原理与实操规范

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:10:38

莱维过程与跳跃扩散:定价、风险度量与模拟校准

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:07:46

SourceTree从安装到日常使用:可视化Git工作流完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华