Aller au contenu

Managing files on GitHub

Sam is away from home, with only a browser, and wants to add a backup script to the homelab. GitHub can create, upload, edit, move, and delete files directly on the website. Each of these actions creates a commit, exactly as git commit would.

sequenceDiagram
    participant B as Browser (github.com)
    participant G as GitHub repository
    participant L as Local clone
    B->>G: Create, upload, edit or delete a file = 1 commit
    B->>G: Another change = another commit
    L->>G: git pull
    G-->>L: The commits made in the browser

Creating a file

On the repository page (Code tab), click Add file > Create new file.

  1. Type the file name in the Name your file... field, next to the repository name.
  2. Write the content in the Edit tab. The Preview tab renders Markdown files.
  3. Click Commit changes....

Creating a directory

GitHub has no "new folder" button: type a / in the file name. Typing scripts/ turns scripts into a directory segment of the path, and the field moves on to the file name: scripts/backup.py creates the scripts directory and the file inside it. Press backspace at the start of the field to edit the directory part again.

Git tracks files, not directories: an empty directory cannot be committed. To keep an empty directory in a repository, the convention is to put an empty file named .gitkeep in it.

The commit dialog

Every change in the browser ends with the same dialog:

Field Purpose
Commit message The first line of the commit message. GitHub suggests one (Create backup.py, Update README.md...): replace it with a meaningful message
Extended description Optional. The body of the commit message: the why of the change
Commit directly to the main branch The commit is added to the current branch
Create a new branch for this commit and start a pull request The commit goes to a new branch (GitHub suggests <username>-patch-1), and GitHub opens a pull request form. The button becomes Propose changes

Sam commits the backup script:

Field Value
Commit message Add NAS backup script
Extended description Copies the Samba shares to the USB disk every night.
Option Commit directly to the main branch

Committing directly to main is fine for a personal project. In a team, or when main is protected, choose the new branch option: see Branches and branch protection and Pull requests.

Uploading files

Add file > Upload files adds files from your computer:

  1. Drag and drop files or folders onto the page, or click choose your files.
  2. Fill in the commit dialog. The default message is Add files via upload.
  3. Click Commit changes.

All the uploaded files go into a single commit: it is the only way to commit several files at once in the basic web interface. To upload into a directory, first open that directory in the file list, then use Add file.

Limit Value
Files per upload 100
Size of a file uploaded in the browser 25 MiB
Size of a file pushed with Git 100 MiB (with a warning from 50 MiB)
Larger files Git Large File Storage (Git LFS)

Editing, renaming, and moving a file

Open the file, then click the pencil icon (Edit this file). The editor is the same as for a new file:

  • Edit the content, and use the Preview tab to check the result, including the changes for files that are not Markdown.
  • Rename the file by changing its name in the name field.
  • Move it by editing the path in the name field: add docs/ to move it into a directory, or press backspace at the start of the field to move it up one level.

Then commit. The default message describes the action: Update inventory.csv, Rename notes.txt to services.md. A rename or move is recorded by Git as a rename, with the history intact:

$ git show --stat --format=%s HEAD
Rename notes.txt to services.md

 notes.txt => services.md | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

Other buttons on a file page give access to the same information as Git commands: Raw (the bare file content), Blame (who last changed each line, like git blame), History (the commits that changed this file, like git log <file>), and the copy and download icons.

Deleting a file

Open the file, then click the ... menu (top right of the file) > Delete file. GitHub shows the deletion as a diff; commit it (default message Delete <file>). A directory can be deleted the same way from its own page, with Delete directory.

Deleting a file removes it from the latest version, not from the history. Any earlier commit still contains it, exactly as with git rm.

A deleted secret is still public

Sam once uploaded a .env file with the NAS password, then deleted it with a second commit. The file is gone from the repository page, but anyone with a clone can still read it:

$ git log --oneline -- .env
aff705a Delete .env
9d32749 Add files via upload
$ git show 9d32749:.env
NAS_PASSWORD=hunter2

The only real fix is to change the password (or revoke the token). Rewriting the history to remove the file is possible, but complex, and does not help against copies made in the meantime. Prevent the problem instead: list secret files in .gitignore, and check what you upload. On public repositories, GitHub's secret scanning and push protection detect many kinds of tokens and can block them.

The result, seen from Git

Every action in the browser is a commit like any other. After Sam's session, a git pull on the desktop brings back:

$ git log --oneline --stat
aff705a Delete .env
 .env | 1 -
 1 file changed, 1 deletion(-)
9d32749 Add files via upload
 .env | 1 +
 1 file changed, 1 insertion(+)
f9e2d87 Add NAS backup script
 scripts/backup.py | 2 ++
 1 file changed, 2 insertions(+)
0963cd0 Create inventory.csv
 inventory.csv | 2 ++
 1 file changed, 2 insertions(+)
2203dfe Initial commit
 README.md | 1 +
 1 file changed, 1 insertion(+)

Browser and local clone: one more copy to keep in sync

A commit made in the browser is a commit the local clones do not have yet. Pull before working locally, or the next push is rejected, exactly as in Pushing to a remote.

Browser or Git?

Task Browser Local Git
Fix a typo, edit one file Quick and easy Clone, edit, commit, push
Change several files in one commit Only through an upload, or github.dev (. key) Natural
Test a change before committing (run a script...) Impossible Natural
Work offline Impossible Natural
Large or many files Limited (25 MiB, 100 files) Up to 100 MiB per file, or Git LFS

Summary

Action Where Default commit message
Create a file Add file > Create new file Create <file>
Create a directory Type / in the file name Create <file>
Upload files Add file > Upload files Add files via upload
Edit a file Pencil icon on the file Update <file>
Rename or move a file Name field in the editor Rename <old> to <new>
Delete a file ... > Delete file Delete <file>

Common mistakes

  • Keeping the default commit message. Update README.md says nothing about what changed, or why.
  • Trying to create an empty directory. Git only tracks files: add a .gitkeep.
  • Deleting a committed secret and considering it safe. It stays in the history: change it.
  • Forgetting to pull after working in the browser. The next local push is rejected.

Hands-on labs

Three labs, from guided to more autonomous. They need your GitHub account and Git. Replace <username> with your GitHub username.

Lab 1: build a repository from the browser

Objective: create, upload, edit, and delete files on GitHub, then find each action as a commit in a local clone.

Prerequisites and initial state: a GitHub account, Git installed. The setup creates a local services.md to upload.

Setup: on GitHub, create a public repository homelab-practice-3 with a README. Then:

mkdir -p ~/git-practice/web-files && cd ~/git-practice/web-files
printf '# Services\n\n- pi-dns: Pi-hole\n- nas: Samba shares\n' > services.md

Tasks:

  1. Create inventory.csv containing a header line hostname,ip,role and the line pi-dns,192.168.1.10,dns. Commit with the message Create inventory.
  2. Create scripts/backup.py, containing print("backup"), with the message Add NAS backup script and the extended description Copies the Samba shares every night.
  3. Upload ~/git-practice/web-files/services.md, with the message Add services list.
  4. Edit inventory.csv to add nas,192.168.1.20,storage. Check the change in the preview, then commit with the message Add NAS.
  5. Delete scripts/backup.py, with the message Remove backup script. Look at what happened to the scripts directory.
  6. In the terminal, clone the repository into ~/git-practice/web-files, and display the history with the files changed by each commit. Then display the full message of Add NAS backup script.

Expected result and verification:

  • After task 5, the scripts directory has disappeared from the repository page: it contained no other file.
  • git log --oneline --stat shows 6 commits, from Initial commit to Remove backup script, each with the file it changed.
  • The full message of Add NAS backup script includes the extended description as its body.
Solution
cd ~/git-practice/web-files
git clone https://github.com/<username>/homelab-practice-3.git
cd homelab-practice-3
git log --oneline --stat
git log -1 --format=%B ":/Add NAS backup script"
  • :/Add NAS backup script designates the latest commit whose message contains this text: handy when you do not know the hash.
  • The directory disappears with its last file: Git has no notion of an empty directory.
  • Each browser action made exactly one commit; the upload would also have been a single commit with several files.

Keep homelab-practice-3 for Lab 2. Clean up the clone: rm -rf ~/git-practice/web-files.

Lab 2: the secret that would not go away

Objective: make the classic mistake on purpose, uploading a secret, and prove that deleting it does not remove it from the history.

Prerequisites and initial state: homelab-practice-3 from Lab 1 (or any practice repository of yours). The setup creates a fake secret file to upload. Use a fake value only.

Setup:

mkdir -p ~/git-practice/secret && cd ~/git-practice/secret
echo "NAS_PASSWORD=not-a-real-password" > .env

Tasks:

  1. Upload ~/git-practice/secret/.env to homelab-practice-3 (your file manager may hide dot files: drag the file from a terminal-opened folder, or use choose your files and show hidden files). Keep the default commit message.
  2. Delete .env on GitHub, with the message Delete .env.
  3. Clone the repository into ~/git-practice/secret. Check that .env is not in the working directory.
  4. With Git, find the commits that touched .env, and display its content in the commit that added it.
  5. On GitHub, find the same information with the History of the repository (N commits link), by opening the upload commit.
  6. Prevent it from happening again: create a .gitignore containing .env on GitHub. Locally, pull, recreate .env, and check that Git ignores it.

Expected result and verification:

  • Task 3: ls -a shows no .env.
  • Task 4: git log --oneline -- .env lists Delete .env and Add files via upload; git show <hash>:.env prints NAS_PASSWORD=not-a-real-password.
  • Task 6: git check-ignore -v .env prints .gitignore:1:.env .env, and git status shows nothing to commit.
Solution
# 3. Not in the latest version...
cd ~/git-practice/secret
git clone https://github.com/<username>/homelab-practice-3.git
cd homelab-practice-3
ls -a

# 4. ...but still in the history
git log --oneline -- .env
git show <hash>:.env               # the hash of "Add files via upload"

# 6. After creating .gitignore on GitHub
git pull
echo "NAS_PASSWORD=not-a-real-password" > .env
git check-ignore -v .env           # .gitignore:1:.env  .env
git status                         # nothing to commit, working tree clean
  • With a real password, the only safe action after task 1 is to change it.
  • git log -- <file> also works for files that no longer exist, as long as you put -- before the name.
  • The .gitignore protects uploads made with Git, not uploads made in the browser: GitHub accepts any file you upload by hand.

Clean up when you are done: rm -rf ~/git-practice/secret, and delete homelab-practice-3 on GitHub.

Lab 3: reorganize a repository from the browser

Objective: restructure a repository using only the web editor, then check from Git that the moves were recorded as renames.

Prerequisites and initial state: a GitHub account, Git installed. The setup prepares the files of a messy repository, to upload in one go.

Setup: on GitHub, create a public repository homelab-practice-4, with no initial files. Then:

mkdir -p ~/git-practice/reorganize/upload && cd ~/git-practice/reorganize/upload
printf '# homelab\n' > README.md
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
printf 'print("backup")\n' > backup.py
printf -- '- pi-dns: Pi-hole\n' > notes.txt

Tasks:

  1. On the empty repository's Quick setup page, use the uploading an existing file link to upload the four files of ~/git-practice/reorganize/upload in a single commit, with the message Import homelab files.
  2. Using only the web editor, reach this layout, with one commit per change and meaningful messages:

    README.md
    inventory.csv
    services.md          (was notes.txt)
    scripts/backup.py    (was backup.py)
    docs/.gitkeep        (new, empty)
    
  3. Clone the repository into ~/git-practice/reorganize, and verify the layout and the history.

Expected result and verification:

  • The first commit contains the four files: git show --stat on it lists 4 files.
  • git log --oneline shows 4 commits: the import, the rename of notes.txt, the move of backup.py, and the creation of docs/.gitkeep.
  • git show --stat on the rename and move commits shows notes.txt => services.md and backup.py => scripts/backup.py with 0 changed lines.
Solution
  • Rename: open notes.txt, pencil icon, change the name to services.md, commit Rename notes.txt to services.md.
  • Move: open backup.py, pencil icon, place the cursor at the start of the name field and type scripts/, commit Move backup script to scripts/.
  • Empty directory: Add file > Create new file, name docs/.gitkeep, no content, commit Add docs directory.
cd ~/git-practice/reorganize
git clone https://github.com/<username>/homelab-practice-4.git
cd homelab-practice-4
find . -path ./.git -prune -o -type f -print | sort
git log --oneline
git show --stat HEAD~2             # notes.txt => services.md
git show --stat HEAD~1             # backup.py => scripts/backup.py
  • HEAD~2 and HEAD~1 assume you made the changes in the order above; otherwise, use the hashes shown by git log --oneline.
  • A move or rename with no content change is detected as a rename (=>), so git log --follow scripts/backup.py still finds the import commit.
  • The upload was the only way to put four files in one commit without Git.

Clean up when you are done: rm -rf ~/git-practice/reorganize, and delete homelab-practice-4 on GitHub.