{"action":"created","comment":{"url":"https://api.github.com/repos/tonistiigi/copy/pulls/comments/189754307","pull_request_review_id":121994309,"id":189754307,"diff_hunk":"@@ -0,0 +1,180 @@\n+package main\n+\n+import (\n+\t\"context\"\n+\t\"flag\"\n+\t\"fmt\"\n+\t\"log\"\n+\t\"os\"\n+\t\"os/exec\"\n+\t\"path\"\n+\t\"strings\"\n+\n+\t\"github.com/containerd/continuity/fs\"\n+\t\"github.com/moby/buildkit/util/appcontext\"\n+\t\"github.com/pkg/errors\"\n+\t\"github.com/tonistiigi/copy/detect\"\n+\t\"github.com/tonistiigi/copy/user\"\n+\tcopy \"github.com/tonistiigi/fsutil/copy\"\n+)\n+\n+// cp with Dockerfile ADD/COPY semantics\n+\n+type opts struct {\n+\tunpack bool\n+\tchown  *copy.ChownOpt\n+\troot   string\n+}\n+\n+type chown struct {\n+\tuid, gid int\n+}\n+\n+func main() {\n+\tvar opt opts\n+\tvar username string\n+\tflag.BoolVar(&opt.unpack, \"unpack\", false, \"\")\n+\tflag.StringVar(&username, \"chown\", \"\", \"\")\n+\n+\tflag.Parse()\n+\targs := flag.Args()\n+\n+\tif username != \"\" {\n+\t\tuid, gid, err := user.GetUser(appcontext.Context(), \"/\", username)\n+\t\tif err != nil {\n+\t\t\tpanic(err)\n+\t\t}\n+\t\topt.chown = &copy.ChownOpt{Uid: int(uid), Gid: int(gid)}\n+\t}\n+\n+\twd, err := os.Getwd()\n+\tif err != nil {\n+\t\tpanic(err)\n+\t}\n+\topt.root = wd\n+\n+\tif err := runCopy(appcontext.Context(), args, opt); err != nil {\n+\t\tpanic(err)\n+\t}\n+}\n+\n+func runCopy(ctx context.Context, args []string, opt opts) error {\n+\tif len(args) < 2 {\n+\t\treturn fmt.Errorf(\"invalid args %v\", args)\n+\t}\n+\n+\tsrcs := args[:len(args)-1]\n+\tisdir := false\n+\n+\tfor i, src := range srcs {\n+\t\tfi, err := os.Lstat(src)\n+\t\tif err == nil && fi.IsDir() {\n+\t\t\tisdir = true\n+\t\t\tsrcs[i] = path.Clean(src) + \"/.\"\n+\t\t}\n+\t}\n+\n+\tif len(srcs) > 1 {\n+\t\tisdir = true\n+\n+\t}\n+\n+\tdest := args[len(args)-1]\n+\n+\t// This handles the case where destination is a symlink.\n+\tif fi, err := os.Lstat(path.Clean(dest)); err == nil {\n+\t\tif fi.Mode() & os.ModeSymlink != 0 {\n+\t\t\tsafeDest, err := fs.RootPath(opt.root, dest)\n+\t\t\tif err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\tif strings.HasSuffix(dest, \"/\") {\n+\t\t\t\tdest = safeDest + \"/\"\n+\t\t\t} else {\n+\t\t\t\tdest = safeDest\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\torigDest := dest\n+\t// If destination is a file, then ensure dest is always a directory by using the parent\n+\tif !strings.HasSuffix(dest, \"/\") && !isdir {\n+\t\tdest = path.Dir(dest)\n+\t}\n+\n+\tif err := os.MkdirAll(dest, 0700); err != nil {\n+\t\treturn err\n+\t}\n+\n+\t// if target is dir extract or copy all\n+\tfi, err := os.Stat(dest)\n+\tif err == nil {\n+\t\tif fi.IsDir() && opt.unpack {\n+\t\t\tfor _, src := range srcs {\n+\t\t\t\tif err := runUnpack(ctx, src, origDest, detect.DetectArchiveType(src), opt); err != nil {\n+\t\t\t\t\treturn err\n+\t\t\t\t}\n+\n+\t\t\t}\n+\t\t\treturn nil\n+\t\t}\n+\t}\n+\n+\t// create destination directory for single archive source\n+\tif opt.unpack && len(srcs) == 1 {\n+\t\ttyp := detect.DetectArchiveType(srcs[0])\n+\t\tif typ != detect.Unknown {\n+\t\t\tif err := os.MkdirAll(origDest, 0700); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\tif err := runUnpack(ctx, srcs[0], origDest, typ, opt); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\treturn nil\n+\t\t}\n+\t}\n+\n+\treturn runCp(ctx, srcs, origDest, opt)\n+}\n+\n+func runCp(ctx context.Context, srcs []string, dest string, opt opts) error {\n+\tfor _, src := range srcs {\n+\t\tif err := copy.Copy(ctx, src, dest, copy.AllowWildcards, func(ci *copy.CopyInfo) {\n+\t\t\tci.Chown = opt.chown\n+\t\t}); err != nil {\n+\t\t\treturn errors.Wrapf(err, \"failed to copy %s to %s\", src, dest)\n+\t\t}\n+\t}\n+\treturn nil\n+}\n+\n+func runUnpack(ctx context.Context, src, dest string, t detect.ArchiveType, opt opts) error {\n+\tif t == detect.Unknown {\n+\t\treturn runCp(ctx, []string{src}, dest, opt)\n+\t}\n+\n+\t// f, err := os.Open(src)\n+\t// if err != nil {\n+\t// \treturn errors.Wrapf(err, \"failed to open %s\", src)\n+\t// }\n+\t//\n+\t// if _, err := archive.Extract(ctx, dest, f); err != nil {\n+\t// \tf.Close()\n+\t// \treturn err\n+\t// }\n+\t// return nil\n+\tflags := \"-xv\"\n+\tswitch t {\n+\tcase detect.Gzip:\n+\t\tflags += \"z\"\n+\tcase detect.Bzip2:\n+\t\tflags += \"j\"\n+\tcase detect.Xz:\n+\t\tflags += \"J\"\n+\t}\n+\tcmd := exec.CommandContext(ctx, \"tar\", flags+\"f\", src, \"-C\", dest)","path":"cmd/copy/main.go","position":175,"original_position":175,"commit_id":"796c6f24df3060227100c4da605ec4651690d21e","original_commit_id":"796c6f24df3060227100c4da605ec4651690d21e","user":{"login":"AkihiroSuda","id":9248427,"avatar_url":"https://avatars2.githubusercontent.com/u/9248427?v=4","gravatar_id":"","url":"https://api.github.com/users/AkihiroSuda","html_url":"https://github.com/AkihiroSuda","followers_url":"https://api.github.com/users/AkihiroSuda/followers","following_url":"https://api.github.com/users/AkihiroSuda/following{/other_user}","gists_url":"https://api.github.com/users/AkihiroSuda/gists{/gist_id}","starred_url":"https://api.github.com/users/AkihiroSuda/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/AkihiroSuda/subscriptions","organizations_url":"https://api.github.com/users/AkihiroSuda/orgs","repos_url":"https://api.github.com/users/AkihiroSuda/repos","events_url":"https://api.github.com/users/AkihiroSuda/events{/privacy}","received_events_url":"https://api.github.com/users/AkihiroSuda/received_events","type":"User","site_admin":false},"body":"Any reason not to use Go tar?\r\n","created_at":"2018-05-22T01:14:57Z","updated_at":"2018-05-22T01:14:58Z","html_url":"https://github.com/tonistiigi/copy/pull/2#discussion_r189754307","pull_request_url":"https://api.github.com/repos/tonistiigi/copy/pulls/2","author_association":"NONE","_links":{"self":{"href":"https://api.github.com/repos/tonistiigi/copy/pulls/comments/189754307"},"html":{"href":"https://github.com/tonistiigi/copy/pull/2#discussion_r189754307"},"pull_request":{"href":"https://api.github.com/repos/tonistiigi/copy/pulls/2"}}},"pull_request":{"url":"https://api.github.com/repos/tonistiigi/copy/pulls/2","id":159331521,"html_url":"https://github.com/tonistiigi/copy/pull/2","diff_url":"https://github.com/tonistiigi/copy/pull/2.diff","patch_url":"https://github.com/tonistiigi/copy/pull/2.patch","issue_url":"https://api.github.com/repos/tonistiigi/copy/issues/2","number":2,"state":"open","locked":false,"title":" copy: switch to go implementation","user":{"login":"tonistiigi","id":585223,"avatar_url":"https://avatars1.githubusercontent.com/u/585223?v=4","gravatar_id":"","url":"https://api.github.com/users/tonistiigi","html_url":"https://github.com/tonistiigi","followers_url":"https://api.github.com/users/tonistiigi/followers","following_url":"https://api.github.com/users/tonistiigi/following{/other_user}","gists_url":"https://api.github.com/users/tonistiigi/gists{/gist_id}","starred_url":"https://api.github.com/users/tonistiigi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tonistiigi/subscriptions","organizations_url":"https://api.github.com/users/tonistiigi/orgs","repos_url":"https://api.github.com/users/tonistiigi/repos","events_url":"https://api.github.com/users/tonistiigi/events{/privacy}","received_events_url":"https://api.github.com/users/tonistiigi/received_events","type":"User","site_admin":false},"body":"Reimplement in go to support changing file owners.","created_at":"2017-12-20T04:59:13Z","updated_at":"2018-05-22T01:14:58Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8eaa6885200004a711ce0153da8b436949073e95","assignee":null,"assignees":[],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/tonistiigi/copy/pulls/2/commits","review_comments_url":"https://api.github.com/repos/tonistiigi/copy/pulls/2/comments","review_comment_url":"https://api.github.com/repos/tonistiigi/copy/pulls/comments{/number}","comments_url":"https://api.github.com/repos/tonistiigi/copy/issues/2/comments","statuses_url":"https://api.github.com/repos/tonistiigi/copy/statuses/796c6f24df3060227100c4da605ec4651690d21e","head":{"label":"tonistiigi:go-copy","ref":"go-copy","sha":"796c6f24df3060227100c4da605ec4651690d21e","user":{"login":"tonistiigi","id":585223,"avatar_url":"https://avatars1.githubusercontent.com/u/585223?v=4","gravatar_id":"","url":"https://api.github.com/users/tonistiigi","html_url":"https://github.com/tonistiigi","followers_url":"https://api.github.com/users/tonistiigi/followers","following_url":"https://api.github.com/users/tonistiigi/following{/other_user}","gists_url":"https://api.github.com/users/tonistiigi/gists{/gist_id}","starred_url":"https://api.github.com/users/tonistiigi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tonistiigi/subscriptions","organizations_url":"https://api.github.com/users/tonistiigi/orgs","repos_url":"https://api.github.com/users/tonistiigi/repos","events_url":"https://api.github.com/users/tonistiigi/events{/privacy}","received_events_url":"https://api.github.com/users/tonistiigi/received_events","type":"User","site_admin":false},"repo":{"id":102167424,"name":"copy","full_name":"tonistiigi/copy","owner":{"login":"tonistiigi","id":585223,"avatar_url":"https://avatars1.githubusercontent.com/u/585223?v=4","gravatar_id":"","url":"https://api.github.com/users/tonistiigi","html_url":"https://github.com/tonistiigi","followers_url":"https://api.github.com/users/tonistiigi/followers","following_url":"https://api.github.com/users/tonistiigi/following{/other_user}","gists_url":"https://api.github.com/users/tonistiigi/gists{/gist_id}","starred_url":"https://api.github.com/users/tonistiigi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tonistiigi/subscriptions","organizations_url":"https://api.github.com/users/tonistiigi/orgs","repos_url":"https://api.github.com/users/tonistiigi/repos","events_url":"https://api.github.com/users/tonistiigi/events{/privacy}","received_events_url":"https://api.github.com/users/tonistiigi/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tonistiigi/copy","description":null,"fork":false,"url":"https://api.github.com/repos/tonistiigi/copy","forks_url":"https://api.github.com/repos/tonistiigi/copy/forks","keys_url":"https://api.github.com/repos/tonistiigi/copy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tonistiigi/copy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tonistiigi/copy/teams","hooks_url":"https://api.github.com/repos/tonistiigi/copy/hooks","issue_events_url":"https://api.github.com/repos/tonistiigi/copy/issues/events{/number}","events_url":"https://api.github.com/repos/tonistiigi/copy/events","assignees_url":"https://api.github.com/repos/tonistiigi/copy/assignees{/user}","branches_url":"https://api.github.com/repos/tonistiigi/copy/branches{/branch}","tags_url":"https://api.github.com/repos/tonistiigi/copy/tags","blobs_url":"https://api.github.com/repos/tonistiigi/copy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tonistiigi/copy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tonistiigi/copy/git/refs{/sha}","trees_url":"https://api.github.com/repos/tonistiigi/copy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tonistiigi/copy/statuses/{sha}","languages_url":"https://api.github.com/repos/tonistiigi/copy/languages","stargazers_url":"https://api.github.com/repos/tonistiigi/copy/stargazers","contributors_url":"https://api.github.com/repos/tonistiigi/copy/contributors","subscribers_url":"https://api.github.com/repos/tonistiigi/copy/subscribers","subscription_url":"https://api.github.com/repos/tonistiigi/copy/subscription","commits_url":"https://api.github.com/repos/tonistiigi/copy/commits{/sha}","git_commits_url":"https://api.github.com/repos/tonistiigi/copy/git/commits{/sha}","comments_url":"https://api.github.com/repos/tonistiigi/copy/comments{/number}","issue_comment_url":"https://api.github.com/repos/tonistiigi/copy/issues/comments{/number}","contents_url":"https://api.github.com/repos/tonistiigi/copy/contents/{+path}","compare_url":"https://api.github.com/repos/tonistiigi/copy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tonistiigi/copy/merges","archive_url":"https://api.github.com/repos/tonistiigi/copy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tonistiigi/copy/downloads","issues_url":"https://api.github.com/repos/tonistiigi/copy/issues{/number}","pulls_url":"https://api.github.com/repos/tonistiigi/copy/pulls{/number}","milestones_url":"https://api.github.com/repos/tonistiigi/copy/milestones{/number}","notifications_url":"https://api.github.com/repos/tonistiigi/copy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tonistiigi/copy/labels{/name}","releases_url":"https://api.github.com/repos/tonistiigi/copy/releases{/id}","deployments_url":"https://api.github.com/repos/tonistiigi/copy/deployments","created_at":"2017-09-02T02:30:02Z","updated_at":"2018-02-02T00:35:16Z","pushed_at":"2018-05-22T01:00:21Z","git_url":"git://github.com/tonistiigi/copy.git","ssh_url":"git@github.com:tonistiigi/copy.git","clone_url":"https://github.com/tonistiigi/copy.git","svn_url":"https://github.com/tonistiigi/copy","homepage":null,"size":962,"stargazers_count":2,"watchers_count":2,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":1,"open_issues":1,"watchers":2,"default_branch":"master"}},"base":{"label":"tonistiigi:master","ref":"master","sha":"726d4feedc8aa4bbd10aeb76f3c6c1a32230fa40","user":{"login":"tonistiigi","id":585223,"avatar_url":"https://avatars1.githubusercontent.com/u/585223?v=4","gravatar_id":"","url":"https://api.github.com/users/tonistiigi","html_url":"https://github.com/tonistiigi","followers_url":"https://api.github.com/users/tonistiigi/followers","following_url":"https://api.github.com/users/tonistiigi/following{/other_user}","gists_url":"https://api.github.com/users/tonistiigi/gists{/gist_id}","starred_url":"https://api.github.com/users/tonistiigi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tonistiigi/subscriptions","organizations_url":"https://api.github.com/users/tonistiigi/orgs","repos_url":"https://api.github.com/users/tonistiigi/repos","events_url":"https://api.github.com/users/tonistiigi/events{/privacy}","received_events_url":"https://api.github.com/users/tonistiigi/received_events","type":"User","site_admin":false},"repo":{"id":102167424,"name":"copy","full_name":"tonistiigi/copy","owner":{"login":"tonistiigi","id":585223,"avatar_url":"https://avatars1.githubusercontent.com/u/585223?v=4","gravatar_id":"","url":"https://api.github.com/users/tonistiigi","html_url":"https://github.com/tonistiigi","followers_url":"https://api.github.com/users/tonistiigi/followers","following_url":"https://api.github.com/users/tonistiigi/following{/other_user}","gists_url":"https://api.github.com/users/tonistiigi/gists{/gist_id}","starred_url":"https://api.github.com/users/tonistiigi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tonistiigi/subscriptions","organizations_url":"https://api.github.com/users/tonistiigi/orgs","repos_url":"https://api.github.com/users/tonistiigi/repos","events_url":"https://api.github.com/users/tonistiigi/events{/privacy}","received_events_url":"https://api.github.com/users/tonistiigi/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/tonistiigi/copy","description":null,"fork":false,"url":"https://api.github.com/repos/tonistiigi/copy","forks_url":"https://api.github.com/repos/tonistiigi/copy/forks","keys_url":"https://api.github.com/repos/tonistiigi/copy/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tonistiigi/copy/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tonistiigi/copy/teams","hooks_url":"https://api.github.com/repos/tonistiigi/copy/hooks","issue_events_url":"https://api.github.com/repos/tonistiigi/copy/issues/events{/number}","events_url":"https://api.github.com/repos/tonistiigi/copy/events","assignees_url":"https://api.github.com/repos/tonistiigi/copy/assignees{/user}","branches_url":"https://api.github.com/repos/tonistiigi/copy/branches{/branch}","tags_url":"https://api.github.com/repos/tonistiigi/copy/tags","blobs_url":"https://api.github.com/repos/tonistiigi/copy/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tonistiigi/copy/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tonistiigi/copy/git/refs{/sha}","trees_url":"https://api.github.com/repos/tonistiigi/copy/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tonistiigi/copy/statuses/{sha}","languages_url":"https://api.github.com/repos/tonistiigi/copy/languages","stargazers_url":"https://api.github.com/repos/tonistiigi/copy/stargazers","contributors_url":"https://api.github.com/repos/tonistiigi/copy/contributors","subscribers_url":"https://api.github.com/repos/tonistiigi/copy/subscribers","subscription_url":"https://api.github.com/repos/tonistiigi/copy/subscription","commits_url":"https://api.github.com/repos/tonistiigi/copy/commits{/sha}","git_commits_url":"https://api.github.com/repos/tonistiigi/copy/git/commits{/sha}","comments_url":"https://api.github.com/repos/tonistiigi/copy/comments{/number}","issue_comment_url":"https://api.github.com/repos/tonistiigi/copy/issues/comments{/number}","contents_url":"https://api.github.com/repos/tonistiigi/copy/contents/{+path}","compare_url":"https://api.github.com/repos/tonistiigi/copy/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tonistiigi/copy/merges","archive_url":"https://api.github.com/repos/tonistiigi/copy/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tonistiigi/copy/downloads","issues_url":"https://api.github.com/repos/tonistiigi/copy/issues{/number}","pulls_url":"https://api.github.com/repos/tonistiigi/copy/pulls{/number}","milestones_url":"https://api.github.com/repos/tonistiigi/copy/milestones{/number}","notifications_url":"https://api.github.com/repos/tonistiigi/copy/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tonistiigi/copy/labels{/name}","releases_url":"https://api.github.com/repos/tonistiigi/copy/releases{/id}","deployments_url":"https://api.github.com/repos/tonistiigi/copy/deployments","created_at":"2017-09-02T02:30:02Z","updated_at":"2018-02-02T00:35:16Z","pushed_at":"2018-05-22T01:00:21Z","git_url":"git://github.com/tonistiigi/copy.git","ssh_url":"git@github.com:tonistiigi/copy.git","clone_url":"https://github.com/tonistiigi/copy.git","svn_url":"https://github.com/tonistiigi/copy","homepage":null,"size":962,"stargazers_count":2,"watchers_count":2,"language":"Go","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":1,"mirror_url":null,"archived":false,"open_issues_count":1,"license":{"key":"mit","name":"MIT License","spdx_id":"MIT","url":"https://api.github.com/licenses/mit"},"forks":1,"open_issues":1,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/tonistiigi/copy/pulls/2"},"html":{"href":"https://github.com/tonistiigi/copy/pull/2"},"issue":{"href":"https://api.github.com/repos/tonistiigi/copy/issues/2"},"comments":{"href":"https://api.github.com/repos/tonistiigi/copy/issues/2/comments"},"review_comments":{"href":"https://api.github.com/repos/tonistiigi/copy/pulls/2/comments"},"review_comment":{"href":"https://api.github.com/repos/tonistiigi/copy/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/tonistiigi/copy/pulls/2/commits"},"statuses":{"href":"https://api.github.com/repos/tonistiigi/copy/statuses/796c6f24df3060227100c4da605ec4651690d21e"}},"author_association":"OWNER"}}
{"actor":{"display_login":"AkihiroSuda"}}