Repository: google/go-github (go)

Issue:
RepositoryContent target field for symlinks
Per the [API docs](https://developer.github.com/v3/repos/contents/#response-if-content-is-a-symlink), when retrieving repository contents and the file is a symlink that doesn't point to a normal file (for example, a directory), the target field is set. The target field is currently missing from [RepositoryContent](https://github.com/google/go-github/blob/master/github/repos_contents.go#L23).

I will open a PR shortly to add the Target field.


Pull request description:
Add Target field to RepositoryContent
When getting file contents and the file is a symlink that points to anything other than a normal file (for example, a directory), the target field is used to indicate this.

This PR adds the Target field and accessor.

Fixes #1106 

Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 658cad2a..abd2a0fe 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -9700,6 +9700,14 @@ func (r *RepositoryContent) GetSize() int {
 	return *r.Size
 }
 
+// GetTarget returns the Target field if it's non-nil, zero value otherwise.
+func (r *RepositoryContent) GetTarget() string {
+	if r == nil || r.Target == nil {
+		return ""
+	}
+	return *r.Target
+}
+
 // GetType returns the Type field if it's non-nil, zero value otherwise.
 func (r *RepositoryContent) GetType() string {
 	if r == nil || r.Type == nil {
diff --git a/github/github.go b/github/github.go
index abed36dd..4b4768f6 100644
--- a/github/github.go
+++ b/github/github.go
@@ -191,7 +191,8 @@ type ListOptions struct {
 
 // UploadOptions specifies the parameters to methods that support uploads.
 type UploadOptions struct {
-	Name string `url:"name,omitempty"`
+	Name      string `url:"name,omitempty"`
+	MediaType string `url:"-"`
 }
 
 // RawType represents type of raw format of a request instead of JSON.
diff --git a/github/repos_contents.go b/github/repos_contents.go
index ffb56b90..bf6cabc5 100644
--- a/github/repos_contents.go
+++ b/github/repos_contents.go
@@ -21,7 +21,10 @@ import (
 
 // RepositoryContent represents a file or directory in a github repository.
 type RepositoryContent struct {
-	Type     *string `json:"type,omitempty"`
+	Type *string `json:"type,omitempty"`
+	// Target is only set if the type is "symlink" and the target is not a normal file.
+	// If Target is set, Path will be the symlink path.
+	Target   *string `json:"target,omitempty"`
 	Encoding *string `json:"encoding,omitempty"`
 	Size     *int    `json:"size,omitempty"`
 	Name     *string `json:"name,omitempty"`
diff --git a/github/repos_releases.go b/github/repos_releases.go
index bf58743e..5c0a1cea 100644
--- a/github/repos_releases.go
+++ b/github/repos_releases.go
@@ -356,6 +356,10 @@ func (s *RepositoriesService) UploadReleaseAsset(ctx context.Context, owner, rep
 	}
 
 	mediaType := mime.TypeByExtension(filepath.Ext(file.Name()))
+	if opt.MediaType != "" {
+		mediaType = opt.MediaType
+	}
+
 	req, err := s.client.NewUploadRequest(u, file, stat.Size(), mediaType)
 	if err != nil {
 		return nil, nil, err


Test patch:
diff --git a/github/repos_releases_test.go b/github/repos_releases_test.go
index d9a93324..971b79c7 100644
--- a/github/repos_releases_test.go
+++ b/github/repos_releases_test.go
@@ -354,32 +354,65 @@ func TestRepositoriesService_DeleteReleaseAsset(t *testing.T) {
 }
 
 func TestRepositoriesService_UploadReleaseAsset(t *testing.T) {
+	uploadTests := []struct {
+		uploadOpts        *UploadOptions
+		fileName          string
+		expectedMediaType string
+	}{
+		// No file extension and no explicit media type.
+		{
+			&UploadOptions{Name: "n"},
+			"upload",
+			defaultMediaType,
+		},
+		// File extension and no explicit media type.
+		{
+			&UploadOptions{Name: "n"},
+			"upload.txt",
+			"text/plain; charset=utf-8",
+		},
+		// No file extension and explicit media type.
+		{
+			&UploadOptions{Name: "n", MediaType: "image/png"},
+			"upload",
+			"image/png",
+		},
+		// File extension and explicit media type.
+		{
+			&UploadOptions{Name: "n", MediaType: "image/png"},
+			"upload.png",
+			"image/png",
+		},
+	}
+
 	client, mux, _, teardown := setup()
 	defer teardown()
 
-	mux.HandleFunc("/repos/o/r/releases/1/assets", func(w http.ResponseWriter, r *http.Request) {
-		testMethod(t, r, "POST")
-		testHeader(t, r, "Content-Type", "text/plain; charset=utf-8")
-		testHeader(t, r, "Content-Length", "12")
-		testFormValues(t, r, values{"name": "n"})
-		testBody(t, r, "Upload me !\n")
-
-		fmt.Fprintf(w, `{"id":1}`)
-	})
-
-	file, dir, err := openTestFile("upload.txt", "Upload me !\n")
-	if err != nil {
-		t.Fatalf("Unable to create temp file: %v", err)
-	}
-	defer os.RemoveAll(dir)
+	for key, test := range uploadTests {
+		releaseEndpoint := fmt.Sprintf("/repos/o/r/releases/%d/assets", key)
+		mux.HandleFunc(releaseEndpoint, func(w http.ResponseWriter, r *http.Request) {
+			testMethod(t, r, "POST")
+			testHeader(t, r, "Content-Type", test.expectedMediaType)
+			testHeader(t, r, "Content-Length", "12")
+			testFormValues(t, r, values{"name": "n"})
+			testBody(t, r, "Upload me !\n")
+
+			fmt.Fprintf(w, `{"id":1}`)
+		})
+
+		file, dir, err := openTestFile(test.fileName, "Upload me !\n")
+		if err != nil {
+			t.Fatalf("Unable to create temp file: %v", err)
+		}
+		defer os.RemoveAll(dir)
 
-	opt := &UploadOptions{Name: "n"}
-	asset, _, err := client.Repositories.UploadReleaseAsset(context.Background(), "o", "r", 1, opt, file)
-	if err != nil {
-		t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err)
-	}
-	want := &ReleaseAsset{ID: Int64(1)}
-	if !reflect.DeepEqual(asset, want) {
-		t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want)
+		asset, _, err := client.Repositories.UploadReleaseAsset(context.Background(), "o", "r", int64(key), test.uploadOpts, file)
+		if err != nil {
+			t.Errorf("Repositories.UploadReleaseAssert returned error: %v", err)
+		}
+		want := &ReleaseAsset{ID: Int64(1)}
+		if !reflect.DeepEqual(asset, want) {
+			t.Errorf("Repositories.UploadReleaseAssert returned %+v, want %+v", asset, want)
+		}
 	}
 }