Creating a repository¶
On the Git server, Sam created the shared repository with git clone --bare. On GitHub, a form does the same job, and can also add the first files of the project.
The "Create a new repository" form¶
Open the form from anywhere on GitHub with the + menu (top right) > New repository, or from the Repositories tab of your profile with the New button.
| Field | Purpose |
|---|---|
| Repository template | Start from the files of a template repository. Leave No template for an empty project |
| Owner | Your personal account, or an organization you can create repositories in |
| Repository name | Short and memorable. Letters, digits, -, _ and .; other characters, such as spaces, are replaced by - |
| Description (optional) | One sentence, shown in the About box and in search results |
| Visibility | Public or Private (see below) |
| Add README | Create a README.md file containing the repository name as a title |
| Add .gitignore | Create a .gitignore file from a template, for a language or tool |
| Add license / Choose a license | Create a LICENSE file with the text of a common license |
The form states which branch will be created: main, unless you changed the default in your settings (Settings > Repositories). Finally, click Create repository.
The link Import a repository, at the top of the form, copies a repository from another hosting service by its URL, history included.
Sam fills in the form for the homelab:
| Field | Value |
|---|---|
| Owner / Repository name | sam-rivera / homelab |
| Description | Notes and inventory of my home servers |
| Visibility | Private |
| README, .gitignore, license | None: the repository will receive the existing history from the desktop |
Public or private¶
| Visibility | Who can see it | Who can change it |
|---|---|---|
| Public | Anyone on the internet, even without an account | You, and the people you choose |
| Private | Only you, and the people you give access to | You, and the people you choose |
| Internal (enterprise organizations only) | Every member of the enterprise | The people chosen in the organization |
In all cases, you choose who can commit: being able to read a public repository does not give the right to change it. Visibility can be changed later in Settings > General > Danger Zone > Change visibility. Private repositories and collaborators are covered in detail on Repository access and collaborators.
Public means public forever
Anything pushed to a public repository can be copied by anyone, immediately: making it private later does not remove the copies. Never push passwords, tokens, or personal data, even "just for a minute". The homelab inventory lists the internal network of a house: a good reason for Sam to keep it private.
The initial files¶
The three checkboxes create the first commit of the repository, called Initial commit, with the selected files.
README¶
The README.md file presents the project. GitHub displays it on the repository page, below the file list. It is so important that it gets its own page.
.gitignore¶
A .gitignore file lists the files that Git must not track: generated files, local configuration, secrets. GitHub provides templates for most languages and tools. The Python template, for example, contains (among many others):
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
# Environments
.env
.envrc
.venv
| Pattern | Matches |
|---|---|
__pycache__/ |
Any directory named __pycache__, at any depth |
*.py[codz] |
Any file ending with .pyc, .pyo, .pyd or .pyz |
.env |
A file or directory named .env: environment variables, often secrets |
# ... |
A comment |
Ignored files do not appear in git status, and git add . skips them. In a clone of a repository with a Python .gitignore, after running a script and creating a .env file with a password:
$ git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
scripts/
nothing added to commit but untracked files present (use "git add" to track)
$ git status --ignored
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
scripts/
Ignored files:
(use "git add -f <file>..." to include in what will be committed)
.env
scripts/__pycache__/
nothing added to commit but untracked files present (use "git add" to track)
To find out which rule ignores a file, use git check-ignore -v. It prints the file, line number, and pattern of the rule; a file that is not ignored prints nothing:
$ git check-ignore -v .env scripts/__pycache__/backup.cpython-314.pyc scripts/backup.py
.gitignore:153:.env .env
.gitignore:2:__pycache__/ scripts/__pycache__/backup.cpython-314.pyc
Adding an ignored file by name is refused, unless forced with -f:
$ git add .env
The following paths are ignored by one of your .gitignore files:
.env
hint: Use -f if you really want to add them.
hint: Disable this message with "git config set advice.addIgnoredFile false"
.gitignore only affects untracked files
A file that is already committed stays tracked, even if a new rule matches it. To stop tracking it, remove it from the index with git rm --cached <file>, then commit. If it contained a secret, the secret is still in the history: change the password or revoke the token.
License¶
Without a license, a public repository is visible, but the default copyright law applies: nobody may legally reuse, modify, or share the code. A license tells others what they can and cannot do with your work. GitHub offers the most common ones:
| License | In short |
|---|---|
| MIT | Do almost anything, as long as the copyright notice is kept. Very permissive |
| Apache 2.0 | Permissive like MIT, with an explicit patent grant |
| GPL 3.0 | Copies and derived works must stay open source under the same license (copyleft) |
GitHub detects the license and displays it in the About box. choosealicense.com helps to pick one. A private project such as the homelab does not need one.
Starting from an empty repository¶
When no file is selected, the repository is created empty: no commit, no branch. GitHub then shows a Quick setup page with the repository URL (HTTPS or SSH) and the commands for the two usual situations.
...or create a new repository on the command line, when the project does not exist yet:
echo "# homelab" >> README.md
git init
git add README.md
git commit -m "first commit"
git branch -M main
git remote add origin https://github.com/sam-rivera/homelab.git
git push -u origin main
...or push an existing repository from the command line, Sam's situation:
git remote add origin https://github.com/sam-rivera/homelab.git
git branch -M main
git push -u origin main
| Command | Purpose |
|---|---|
git remote add origin <url> |
Register the GitHub repository as the remote origin |
git branch -M main |
Rename the current branch to main, even if a branch with that name already exists. Useful when Git created master |
git push -u origin main |
Send main to GitHub, which creates the branch there, and set the upstream |
These are the commands from Intermediate Git: GitHub is just another remote. The first push creates main on GitHub:
$ git push -u origin main
To https://github.com/sam-rivera/homelab.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
Over HTTPS, the push asks for your username and a personal access token, not your password: see Authenticating with personal access tokens.
Why an empty repository for an existing project¶
If Sam had ticked Add README, the GitHub repository would start with its own Initial commit, unrelated to the history of the desktop. The first push is then rejected, like any push to a remote that has commits you do not have:
$ git push -u origin main
To https://github.com/sam-rivera/homelab.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'https://github.com/sam-rivera/homelab.git'
And the usual fix, pulling first, fails too, because the two histories have no commit in common:
$ git pull origin main
From https://github.com/sam-rivera/homelab
* branch main -> FETCH_HEAD
* [new branch] main -> origin/main
fatal: refusing to merge unrelated histories
It can be fixed with git pull --allow-unrelated-histories origin main, which creates a merge commit joining the two histories. But the simplest is to avoid it: to publish an existing repository, create the GitHub repository empty. Add a README, .gitignore, or license when the project starts on GitHub.
Managing a repository after creation¶
The repository page shows the files of the default branch, and the About box (edit it with the gear icon) for the description, a website, and topics (keywords such as homelab, raspberry-pi).
Settings > General holds the rest:
| Setting | Effect |
|---|---|
| Repository name > Rename | Rename the repository. GitHub redirects the old URL, but update your remotes with git remote set-url origin <new-url> |
| Template repository | Allow others to create new repositories from this one's files |
| Default branch | Choose which branch is shown and used as the base for pull requests |
| Danger Zone > Change visibility | Switch between public and private |
| Danger Zone > Archive this repository | Make it read-only, but keep it |
| Danger Zone > Delete this repository | Delete it, after typing its full name to confirm |
Summary¶
| Action | Where |
|---|---|
| Create a repository | + > New repository |
| Add the first files | Add README, Add .gitignore, Add license in the form |
| Publish an existing local repository | Create it empty, then git remote add origin <url> and git push -u origin main |
| See which rule ignores a file | git check-ignore -v <file> |
| List ignored files | git status --ignored |
| Rename, change visibility, delete | Settings > General |
Common mistakes¶
- Initializing the repository with a README before pushing an existing project. The first push is rejected, and the pull refuses to merge unrelated histories.
- Making a repository public "for a moment". Anything published can be copied immediately.
- Expecting
.gitignoreto hide a file that is already committed. Rules only apply to untracked files. - Publishing a public project without a license. Nobody can legally reuse it.
Hands-on labs¶
Three labs, from guided to more autonomous. Labs 1 and 3 need your GitHub account; Lab 2 runs entirely in the terminal. Replace <username> with your GitHub username.
Lab 1: create a repository with its initial files¶
Objective: create a repository with a README, a .gitignore, and a license, then check locally that the .gitignore works.
Prerequisites and initial state: a GitHub account, Git installed. No repository named homelab-practice-1 in your account.
Setup:
mkdir -p ~/git-practice/create && cd ~/git-practice/create
Tasks:
- On GitHub, create a public repository
homelab-practice-1, with the descriptionPractice repository, a README, the Python.gitignoretemplate, and the MIT license. - On the repository page, note the number of commits and its message, the files, and what the About box shows.
- Open
.gitignoreon GitHub, and find the lines for__pycache__/and.env. - In the terminal, clone the repository into
~/git-practice/create(a public repository can be cloned without signing in). - In the clone, create
scripts/backup.pycontainingprint("backup"), a file.envcontainingNAS_PASSWORD=secret, and runpython3 scripts/backup.py(or, without Python, create an emptyscripts/__pycache__/backup.pycby hand). - Check which files Git sees, which ones it ignores, and which rule ignores each of them.
Expected result and verification:
- The repository has 1 commit,
Initial commit, with.gitignore,LICENSEandREADME.md. The About box shows the description, Readme, and MIT license. git statusonly showsscripts/as untracked;git status --ignoredlists.envandscripts/__pycache__/under Ignored files.git check-ignore -v .envprints a line of the form.gitignore:<line>:.env .env.
Solution
# 4. Clone
cd ~/git-practice/create
git clone https://github.com/<username>/homelab-practice-1.git
cd homelab-practice-1
ls -a # . .. .git .gitignore LICENSE README.md
# 5. A script, its compiled cache, and a secret
mkdir scripts
echo 'print("backup")' > scripts/backup.py
echo "NAS_PASSWORD=secret" > .env
python3 scripts/backup.py
python3 -m py_compile scripts/backup.py # creates scripts/__pycache__/
# 6. What Git sees and ignores
git status
git status --ignored
git check-ignore -v .env scripts/__pycache__/*
python3 scripts/backup.pyalone does not create__pycache__: Python only caches imported modules.py_compileforces it.- The exact line numbers depend on the version of GitHub's template; the pattern shown is what matters.
Clean up when you are done: rm -rf ~/git-practice/create. Keep homelab-practice-1 on GitHub if you want to reuse it on the next page; otherwise delete it in Settings > General > Danger Zone > Delete this repository.
Lab 2: publish an existing project, rehearsal¶
Objective: run the Quick setup commands of an empty GitHub repository, against a local bare repository standing in for GitHub, from a repository whose branch is called master.
Prerequisites and initial state: Git installed. The setup creates, in ~/git-practice/quick-setup, an empty bare repository fake-github/homelab.git, and a local project homelab with one commit on a branch named master.
Setup:
mkdir -p ~/git-practice/quick-setup && cd ~/git-practice/quick-setup
git init -q --bare -b main fake-github/homelab.git
git init -q -b master homelab
cd homelab
git config user.name "Sam Rivera"
git config user.email "[email protected]"
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"
Tasks:
- In
homelab, check the current branch name. - Run the three commands of ...or push an existing repository from the command line, with
../fake-github/homelab.gitas the URL. - Check the branch name again, and the upstream of the branch.
- Make a change to
inventory.csv, commit it, and push it with a plaingit push. - Clone the "GitHub" repository into
check, and verify that it contains both commits.
Expected result and verification:
- At task 1, the branch is
master; after task 2, it ismain. - The push prints
* [new branch] main -> mainandbranch 'main' set up to track 'origin/main'. git branch -vvshows[origin/main].- Task 4 works without arguments, thanks to the upstream. In
check,git log --onelineshows the two commits.
Solution
cd ~/git-practice/quick-setup/homelab
git branch # * master
# 2. Quick setup commands
git remote add origin ../fake-github/homelab.git
git branch -M main
git push -u origin main # * [new branch] main -> main
# 3. Branch and upstream
git branch -vv # * main ... [origin/main] Create inventory
# 4. Next pushes are short
echo "nas,192.168.1.20,storage" >> inventory.csv
git commit -am "Add NAS"
git push
# 5. Check from a fresh clone
cd ..
git clone fake-github/homelab.git check
git -C check log --oneline
- On GitHub, the URL would be
https://github.com/<username>/homelab.git, and the push would ask for credentials: the rest is identical. git branch -M mainis why the quick setup works whatever your local branch is called.
Clean up when you are done: rm -rf ~/git-practice/quick-setup.
Lab 3: the README that got in the way¶
Objective: reproduce the classic mistake of initializing the GitHub repository with a README before pushing an existing project, then repair it; and decide how Sam should create the real repository.
Prerequisites and initial state: Git installed. The setup creates a "GitHub" repository in ~/git-practice/unrelated that already contains an Initial commit with a README (as if Add README had been ticked), and a local homelab project with its own history.
Setup:
mkdir -p ~/git-practice/unrelated && cd ~/git-practice/unrelated
git init -q --bare -b main fake-github/homelab.git
git clone -q fake-github/homelab.git tmp 2>/dev/null
cd tmp
git config user.name "GitHub"
git config user.email "[email protected]"
echo "# homelab" > README.md
git add . && git commit -q -m "Initial commit" && git push -q origin main
cd .. && rm -rf tmp
git init -q -b main homelab
cd homelab
git config user.name "Sam Rivera"
git config user.email "[email protected]"
git config pull.rebase false
printf 'hostname,ip,role\npi-dns,192.168.1.10,dns\n' > inventory.csv
git add . && git commit -q -m "Create inventory"
Tasks:
- In
homelab, add../fake-github/homelab.gitasorigin, and pushmainwith an upstream. Read the error. - Pull
mainfromorigin, as the hint suggests. Read the error. - Repair the situation so that the push succeeds, without losing either commit.
- Answer, for the real repository: which visibility, and which initial files, should Sam choose for
sam-rivera/homelab? And for a new public project with no files yet?
Expected result and verification:
- Task 1:
! [rejected] main -> main (fetch first). Task 2:fatal: refusing to merge unrelated histories. - After task 3,
git log --oneline --graphshows a merge commit joiningInitial commitandCreate inventory, and the push succeeds. - Task 4: private, with no initial files (an empty repository); for a new public project, a README, a
.gitignorefor its language, and a license.
Solution
cd ~/git-practice/unrelated/homelab
# 1. Rejected: the remote has a commit we do not have
git remote add origin ../fake-github/homelab.git
git push -u origin main # ! [rejected] main -> main (fetch first)
# 2. The histories share no commit
git pull origin main # fatal: refusing to merge unrelated histories
# 3. Join them explicitly, then push
git pull --allow-unrelated-histories --no-edit origin main
git log --oneline --graph
git push -u origin main
--allow-unrelated-historiesis safe here because the README does not conflict with any local file. If both sides had aREADME.md, you would resolve a conflict as usual.- The alternative, when the GitHub repository has nothing worth keeping, is to delete it and create it again, empty.
- The homelab inventory describes a private network: private visibility, and no initial files since the history already exists.
Clean up when you are done: rm -rf ~/git-practice/unrelated.