Last updated on
Single-user version control
Below is an unedited transcript of the Git command line session that I presented in class in 2023; I have simply added commentary between the individual commands. I kept the mistakes unedited to match exactly what happened in class. The 2025 demo followed roughly the same plan.
I started at an empty command line, running GNU Bash on Ubuntu:
~/git/cs214 $
(The part before and including the $ sign is called the “prompt”. Here I use it to display the current directory.)
I wanted to demo a new Scala project, so I created a directory with mkdir:
~/git/cs214 $ mkdir demo
… but immediately after I realized that I actually didn’t need that directory, because sbt new (the sbt command to make a new project) already creates a directory. So, I tried to delete the directory I had just created:
~/git/cs214 $ rm demo
rm: cannot remove 'demo': Is a directory
That didn’t work, because rm is for files. For directories the right command is rm -r, or rmdir… and I mistyped that too, forgetting the name of the directory:
~/git/cs214 $ rmdir
rmdir: missing operand
Try 'rmdir --help' for more information.
Finally I managed to get rid of demo/:
~/git/cs214 $ rmdir demo
After that, I showed how to get help on an sbt command, using sbt help:
~/git/cs214 $ sbt help init new [--options] <template> Create a new sbt build based on the given template. sbt provides out-of-the-box support for Giter8 templates. See foundweekends.org/giter8/ for details.
Example: sbt new scala/scala-seed.g8
Running sbt new creates a directory and a fresh Scala project from a template (scala/scala3.g8 is the name of the template):
~/git/cs214 $ sbt new scala/scala3.g8 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details. A template to demonstrate a minimal Scala 3 application
name [Scala 3 Project Template]: demo
Template applied in /home/cpitclaudel/git/cs214/./demo
The warning relates to a logging component, and reading the link suggests I don’t have to worry about it.
Then I checked on what sbt new created, which showed a new demo folder:
~/git/cs214 $ ls backup cs214-lectures demo
So I navigated to it using cd:
~/git/cs214 $ cd demo
And I inspected its contents with ls:
~/git/cs214/demo $ ls build.sbt project README.md src
This is when the Git part of the demo began.
I wanted to track my progress with git, so I useed git init to create a repo. And with ls -a and ls .git I confirmed that a new .git hidden folder was indeed created.
~/git/cs214/demo $ git init
Initialized empty Git repository in /home/cpitclaudel/git/cs214/demo/.git/
~/git/cs214/demo $ ls -a . .. build.sbt .git .gitignore project README.md src
~/git/cs214/demo $ ls .git branches config description HEAD hooks info objects refs
Another check of the directory contents with ls:
~/git/cs214/demo $ ls build.sbt project README.md src
By typing git and pressing TAB, I got autocompletion for all available commands. If you want to learn more about any of them, for example restore, then you can use git help [command], for example git help restore.
~/git/cs214/demo $ git
add citool gc mergetool replace stage
am clean gitk mv request-pull stash
apply clone grep notes reset status
archive commit gui prune restore submodule
bisect config help pull revert switch
blame describe init push rm tag
branch diff instaweb range-diff send-email whatchanged
bundle difftool latexdiff rebase shortlog worktree
checkout fetch log reflog show
cherry format-patch maintenance remote show-branch
cherry-pick fsck merge repack sparse-checkout
The first important command is git status, which tells me what I have in my history. Here, the message indicated that I had multiple files whose history I was not recording yet (“untracked”):
~/git/cs214/demo $ git status On branch main
No commits yet
Untracked files: (use "git add <file>..." to include in what will be committed) .gitignore README.md build.sbt project/ src/
nothing added to commit but untracked files present (use "git add" to track)
Git has three places where it tracks information: the workspace, the history, and the “index”:
- The workspace is the folder that contains your files. That’s where you make your changes.
- The history is where git stores snapshots (“commits”, or “changesets”)
- The index is where you temporarily gather changes before taking a snapshot.
The index is a very convenient concept: it lets you collect changes to multiple files into a consistent changeset step by step, by running the git add command. So, when I want git to record a collection of changes (additions, deletions, or modifications), I use git add on each of the modified files (or even on sections of these files—I can select just a few modified lines!), and finally when I have everything ready I use git commit to give a description of the changes that are currently waiting in the index and add them to the history as a new commit.
Here, I used git add -A to add all untracked files at once. This is the same as git add ., which tells git to “stage” everything into the index.
~/git/cs214/demo $ git add -A
After this command, the index matched my workspace exactly, and hence git status now reported that I had new files (staged “changed to be committed”):
~/git/cs214/demo $ git status On branch main
No commits yet
Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: README.md new file: build.sbt new file: project/build.properties new file: src/main/scala/Main.scala new file: src/test/scala/MySuite.scala
I mentioned earlier that git help is a good way to get help on Git commands. In Unix-like environments (macOS, GNU/Linux, WSL), the man (for “manual”) command can also be used to browse documentation (at this point I demoed it because I was asked about git add . vs git add -A). I said I was 60% sure of the answer, and it turned out I was wrong (but the text above is correct).
~/git/cs214/demo $ man git-add
My files are still unchanged:
~/git/cs214/demo $ ls build.sbt project README.md src
~/git/cs214/demo $ git status On branch main
No commits yet
Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: README.md new file: build.sbt new file: project/build.properties new file: src/main/scala/Main.scala new file: src/test/scala/MySuite.scala
Now I’m ready for my first commit! Here I used the command without additional arguments, which opened a text editor in which I typed my “commit message”, which is a summary of my changes (“Create new project with SBT”).
~/git/cs214/demo $ git commit
[main (root-commit) 12a3a4a] Create new project with SBT
6 files changed, 67 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 build.sbt
create mode 100644 project/build.properties
create mode 100644 src/main/scala/Main.scala
create mode 100644 src/test/scala/MySuite.scala
After a commit, Git reports that I do not have changes (“working tree” is the same as “workspace”, and “clean” means no changes):
~/git/cs214/demo $ git status
On branch main
nothing to commit, working tree clean
At this point I opened VSCode to write some code:
~/git/cs214/demo $ code .
… and after making the changes and saving I could see that I had modified Main.scala:
~/git/cs214/demo $ git status On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/main/scala/Main.scala
no changes added to commit (use "git add" and/or "git commit -a")
I then staged Main.scala:
~/git/cs214/demo $ git add src/main/scala/Main.scala
And created a new commit, but this time I used -m to give the commit message directly instead of opening a text editor.
~/git/cs214/demo $ git commit -m "Say hello to EPFL, not to the world"
[main a4a9c57] Say hello to EPFL, not to the world
1 file changed, 1 insertion(+), 3 deletions(-)
Now that I had multiple commits, I could inspect the history of my project, using git log:
~/git/cs214/demo $ git log commit a4a9c57b99279f66e831530f7397e98005bf1852 (HEAD -> main) Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:44:36 2023 +0200
Say hello to EPFL, not to the world
commit 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:42:21 2023 +0200
Create new project with SBT
By adding -p to git log I got “diffs” in addition to commit messages (a “diff” is a comparison of the contents of two files). Red means removed, and green means added:
~/git/cs214/demo $ git log -p commit a4a9c57b99279f66e831530f7397e98005bf1852 (HEAD -> main) Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:44:36 2023 +0200
Say hello to EPFL, not to the world
diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index a0c585f..5319870 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,5 +1,3 @@ @main def hello: Unit = - println("Hello world!") - println(msg) + println("Hello EPFL")
-def msg = "I was compiled by Scala 3. :)"
commit 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:42:21 2023 +0200
Create new project with SBT
At this point I chose to compile and run my code…
~/git/cs214/demo $ sbt run [info] welcome to sbt 1.9.6 (Ubuntu Java 11.0.20.1) [info] loading settings for project demo-build-build from metals.sbt ... [info] loading project definition from /home/cpitclaudel/git/cs214/demo/project/project [info] loading settings for project demo-build from metals.sbt ... [info] loading project definition from /home/cpitclaudel/git/cs214/demo/project [success] Generated .bloop/demo-build.json [success] Total time: 1 s, completed Sep 27, 2023, 3:45:24 PM [info] loading settings for project root from build.sbt ... [info] set current project to demo (in build file:/home/cpitclaudel/git/cs214/demo/) [info] compiling 1 Scala source to /home/cpitclaudel/git/cs214/demo/target/scala-3.3.1/classes ... [info] running hello Hello EPFL [success] Total time: 3 s, completed Sep 27, 2023, 3:45:28 PM
… but I was disappointed: I forgot the “!” at the end of the message! So I went back to my existing VSCode window and modified the code further:
~/git/cs214/demo $ git status On branch main Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git restore <file>..." to discard changes in working directory) modified: src/main/scala/Main.scala
no changes added to commit (use "git add" and/or "git commit -a")
I then used git diff to confirm the changes I had made:
~/git/cs214/demo $ git diff diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 5319870..b77e767 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,3 +1,3 @@ @main def hello: Unit = - println("Hello EPFL") + println("Hello EPFL!")
… git add to stage them into the index:
~/git/cs214/demo $ git add src/main/scala/Main.scala
… and git commit to create a snapshot… except that I decided, rather than create an additional entry in the history, to rewrite the previous history entry by pretending I got the “!” right from the start. That’s what the --amend is for: it combines my stages changes into the most recent changeset/commit rather than create a new one (note that I also added a “!” to the amended commit message).
~/git/cs214/demo $ git commit --amend
[main 56e8387] Say hello to EPFL, not to the world!
Date: Wed Sep 27 15:44:36 2023 +0200
1 file changed, 1 insertion(+), 3 deletions(-)
I then checked git log to make sure that I had indeed modified the most recent commit:
~/git/cs214/demo $ git log commit 56e83877f6c28aeede83335d7a2deb5d59282402 (HEAD -> main) Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:44:36 2023 +0200
Say hello to EPFL, not to the world!
commit 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:42:21 2023 +0200
Create new project with SBT
Notice the long yellow identifiers above (hexadecimal, next to the word “commit”): these are commit hashes, used to identify specific commits in the history of your project unambiguously. Using git show, I can see a commit by its id (and since I picked the initial commit, the diff is just a long list of additions):
~/git/cs214/demo $ git show 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 commit 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:42:21 2023 +0200
Create new project with SBT
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e79245 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# macOS +.DS_Store + +# sbt specific +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +project/local-plugins.sbt +.history +.ensime +.ensime_cache/ +.sbt-scripted/ +local.sbt + +# Bloop +.bsp + +# VS Code +.vscode/ + +# Metals +.bloop/ +.metals/ +metals.sbt + +# IDEA +.idea +.idea_modules +/.worksheet/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..102c5ca --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +## sbt project compiled with Scala 3 + +### Usage + +This is a normal sbt project. You can compile code with `sbt compile`, run it with `sbt run`, and `sbt console` will start a Scala 3 REPL. + +For more information on the sbt-dotty plugin, see the +[scala3-example-project](https://github.com/scala/scala3-example-project/blob/main/README.md). diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..630e51d --- /dev/null +++ b/build.sbt @@ -0,0 +1,12 @@ +val scala3Version = "3.3.1" + +lazy val root = project + .in(file(".")) + .settings( + name := "demo", + version := "0.1.0-SNAPSHOT", + + scalaVersion := scala3Version, + commit 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:42:21 2023 +0200
Create new project with SBT
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9e79245 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +# macOS +.DS_Store + +# sbt specific +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +project/local-plugins.sbt +.history
I felt happy with my code, so I “tagged” it, which is a way to give a human-readable name to a specific commit (so people can refer to v1.0 instead of 56e83877f6c28aeede83335d7a2deb5d59282402):
~/git/cs214/demo $ git tag -m "Release my beautiful new software" v1.0
… and now we see the tag in the log (look for v1.0).
~/git/cs214/demo $ git log commit 56e83877f6c28aeede83335d7a2deb5d59282402 (HEAD -> main, tag: v1.0) Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:44:36 2023 +0200
Say hello to EPFL, not to the world!
commit 12a3a4a9592d8c3f3f0d0b88a379e0e0125c43d6 Author: Clément Pit-Claudel <clement.pit-claudel@epfl.ch> Date: Wed Sep 27 15:42:21 2023 +0200
Create new project with SBT
Finally, I demonstrated how to make a patch. A patch is a self-contained textual representation of a changeset. Traditional patches only include position markers and +/- pairs; Git patches additionally have a header that includes the author’s name, the authoring date, and the commit message. Here, I used VSCode again to make one more change, then staged it and committed it:
~/git/cs214/demo $ code .
~/git/cs214/demo $ git add src/main/scala/Main.scala
~/git/cs214/demo $ git commit -m "Say hi to just CS214 folks"
[main 12d6eef] Say hi to just CS214 folks
1 file changed, 1 insertion(+), 2 deletions(-)
And after that, I exported the commit to a patch using the git format-patch HEAD~1 command. HEAD is the commit that my repository is currently focused on, and HEAD~1 is the previous commit, so the command below asks Git to export only the latest changeset/commit (from HEAD~1 to HEAD) to a textual patch. Git prints the name of the result, which I then opened in vscode to look at it.
~/git/cs214/demo $ git format-patch HEAD~1
0001-Say-hi-to-just-CS214-folks.patch
~/git/cs214/demo $ code 0001-Say-hi-to-just-CS214-folks.patch
That was the end of the first part, on Git basics. In the second part, I showed some advanced history-browsing techniques. Try to explore them on your own to familiarize yourself with your tools!
For this part, to save time, I used Git repositories that I had already cloned. As a result I didn’t get to demonstrate the git clone command (which you all have been using for the labs):
~/git/cs214/demo $ ls 0001-Say-hi-to-just-CS214-folks.patch build.sbt project README.md src target
~/git/cs214/demo $ cd ..
Here I pressed tab to show completions, then navigated to the vscode repo:
~/git/cs214 $ cd
backup/ cs214-lectures/ demo/
~/git/cs214 $ cd backup/
~/git/cs214/backup $ ls demo dotty find find-2 vscode
~/git/cs214/backup $ cd vscode
The git shortlog command shows me a summary of changes, and I can pair it with the --since flag to filter by date. Below I’ve truncated the list, since it was quite long:
~/git/cs214/backup/vscode $ git shortlog --since=2023-09-20
Aaron Munger (14):
dont remove decorations added by other entries
Merge pull request #193622 from microsoft/aamunger/symbolsDecorations
skip flaky test
Merge pull request #193729 from microsoft/aamunger/flakyTest
Merge pull request #193810 from microsoft/revert-192602-ai-codefixes
anchor to focused cell setting
fix anchor range check
spelling
Merge pull request #193833 from microsoft/aamunger/anchorFocusedCell
resolve cell text models so that we get the symbols even for cells outside the view
update test
Merge pull request #193854 from microsoft/aamunger/notebookSymbols
highlight symbol row in notebooks (#193845)
revert default behavior back to previous
…
Since the list was so long, I chose to focus on just one subdirectory, cli/:
~/git/cs214/backup/vscode $ git shortlog --since=2023-09-20 -- cli/ Bhavya U (1): Update license info from OSS Tool (#194025)
Connor Peet (3): cli: add more details if untar fails cli: fix renamed self-update name, cleanup old binaries cli: support setting the --parent-process-id in command shell (#193735)
Changing the date showed me more commits:
~/git/cs214/backup/vscode $ git shortlog --since=2023-09-01 -- cli/ Bhavya U (1): Update license info from OSS Tool (#194025)
Connor Peet (9): forwarding: fix log format again (#191941) tunnels: fix command prompt windows show up on windows machine (#192016) cli: fix delegated http requests not working (#192620) cli: propagate server closing (#192824) cli: fix panic if it can't bind a port (#193019) cli: allow command-shell to listen on a prescribed port (#193028) cli: add more details if untar fails cli: fix renamed self-update name, cleanup old binaries cli: support setting the --parent-process-id in command shell (#193735)
I find this command is very helpful for catching up after a few weeks of absence from an active project.
Then, I demoed how Git could help understand a complex, evolving codebase. I navigated to the directory in which I has already cloned the Scala 3 compiler:
~/git/cs214/backup/vscode $ cd ../
~/git/cs214/backup $ ls demo dotty find find-2 vscode
~/git/cs214/backup $ cd dotty/
~/git/cs214/backup/dotty $ ls bench community-build language-server presentation-compiler sbt-test stdlib-bootstrapped-tasty-tests bench-micro compiler library project scaladoc tasty bench-run CONTRIBUTING.md library-js README.md scaladoc-js tasty-inspector bin dist LICENSE sandbox scaladoc-testcases tests build.sbt docs MAINTENANCE.md sbt-bridge sjs-compiler-tests changelogs interfaces NOTICE.md sbt-community-build staging
… and, since I wasn’t sure which file to go look at, I wrote a small shell script to find the most commonly modified files (the shell is environment in which I type my command line programs, and a script is a small program). To do this I asked Git to print, for each commit, the list of all modified files. Below I used head to show just a few (in the original demo I used less to page through, so I changed this part):
~/git/cs214/backup/dotty $ git log --format= --name-only | head
tests/run/StringConcat.scala
compiler/src/dotty/tools/dotc/transform/ExtensionMethods.scala
compiler/src/dotty/tools/dotc/util/Signatures.scala
presentation-compiler/test/dotty/tools/pc/tests/signaturehelp/SignatureHelpSuite.scala
bench-run/src/main/scala/dotty/tools/benchmarks/Main.scala
bench/scripts/collection-vector.sh
bench/scripts/compiler-cold.sh
bench/scripts/compiler.sh
bench/scripts/library-cold.sh
bench/scripts/library.sh
I constructed a more complex script by adding a phase to sort the results alphabetically and count them:
~/git/cs214/backup/dotty $ git log --format= --name-only | sort | uniq -c | head
10 .appveyor.yml
17 AUTHORS.md
2 bench-micro/results_isStable.json
1 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/ContendedInitialization.scala
3 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessAny.scala
3 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessGeneric.scala
1 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessInt.scala
2 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessMultiple.scala
4 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccess.scala
3 bench-micro/src/main/scala/dotty/tools/benchmarks/lazyvals/InitializedAccessString.scala
And finally I sorted the results again, this time by number of changes:
~/git/cs214/backup/dotty $ git log --format= --name-only | sort | uniq -c | sort -rn | head
1322 compiler/src/dotty/tools/dotc/typer/Typer.scala
1173 project/Build.scala
992 compiler/src/dotty/tools/dotc/core/Types.scala
812 compiler/src/dotty/tools/dotc/parsing/Parsers.scala
715 compiler/src/dotty/tools/dotc/core/Definitions.scala
633 src/dotty/tools/dotc/core/Types.scala
550 compiler/src/dotty/tools/dotc/core/TypeComparer.scala
529 compiler/src/dotty/tools/dotc/typer/Applications.scala
521 compiler/src/dotty/tools/dotc/ast/Desugar.scala
516 compiler/src/dotty/tools/dotc/typer/Implicits.scala
(I didn’t mention this in the lecture, but the process library in Scala has very nice operators to build pipelines of commands like the above)
I picked Types.scala for no particular reason, except that I hoped it might be a bit simpler than Typer.scala. I opened it in an editor called Emacs, which has great support for Git through a plug-in called magit (I don’t know VSCode as well, and it’s a great exercise for you to figure it out!):
~/git/cs214/backup/dotty $ emacs compiler/src/dotty/tools/dotc/core/Types.scala
Of course since the rest happened in Emacs the command line transcript does not show the details of this part, but I also don’t expect you to learn Emacs for this class, so I will instead mention what I did:
-
I picked a random line (500 at first, then 1000 since the code didn’t look too interesting around line 500), found two very similar functions next to one that was a bit different (
final def implicitMembers), and explored the history of that one. It turned out that it used to be similar to the other two, but diverged (that is, was changed) recently. -
I used
git blameto annotate every line of the filecompiler/src/dotty/tools/dotc/core/Types.scalawith authorship information, showing how much editing had happened in this file after its creation.
~/git/cs214/backup/dotty $
This is the end of part 2!