From bacc1c866e2c30b4939292056d273289757f1124 Mon Sep 17 00:00:00 2001 From: Suzanne Soy <ligo@suzanne.soy> Date: Fri, 18 Jun 2021 19:37:26 +0100 Subject: [PATCH] better error messages, empty filesystem, WIP on log --- git-tutorial.css | 5 ++++ git-tutorial.js | 77 ++++++++++++++++++++++++++++++++++++++++++++---- index.html | 75 +++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 147 insertions(+), 10 deletions(-) diff --git a/git-tutorial.css b/git-tutorial.css index dac14bc..1726be5 100644 --- a/git-tutorial.css +++ b/git-tutorial.css @@ -51,6 +51,7 @@ article#git-tutorial table { width: 77rem; margin-left: calc( ( ( 63rem - 77rem #git-tutorial tr:last-child td { border:thin solid #aaa; } #git-tutorial tr:hover td { opacity: 1; border:thin solid black; } #git-tutorial tr:last-child.different td, #git-tutorial .different td { border: thin solid black; opacity: 1; background: #f0f6f8; } +#git-tutorial table .empty-filesystem { text-align: center !important; color: #444 !important; opacity: 1 !important; border: thin solid black !important; } #git-tutorial .specialchar { color: red; word-wrap: normal; } #git-tutorial .hex-prefix { color: lightgrey; } #git-tutorial .hex { color: brown; } @@ -90,3 +91,7 @@ article#git-tutorial .onlytoc { display: none; } /* Highlight elements when a click on e.g. a hash scrolls to the destination */ #git-tutorial .scroll-destination-hilite, #git-tutorial .scroll-destination-hilite td { transition: background 0.5s linear 0.5s, opacity 0.4s linear 0.5s; background: #ffd3d3 !important; opacity: 1 !important; } #git-tutorial .scroll-destination-lolite, #git-tutorial .scroll-destination-lolite td { transition: background linear 0.5s, opacity linear 0.5s; } +#git-tutorial .trivia { opacity: 80%; border: thin solid slategrey; margin: 1em; padding: 0.3em; } +#git-tutorial .trivia:before { content: "Trivia"; border-bottom: thin solid slategrey; display: block; text-align: center; } +#git-tutorial .trivia table { min-width: 90%; max-width: 90%; width: 90%; margin-left: auto; margin-right: auto; } +/*#git-tutorial .trivia table td.cell-contents, #git-tutorial .trivia table th.cell-contents { width: 30%; }*/ \ No newline at end of file diff --git a/git-tutorial.js b/git-tutorial.js index 43a15dd..cb49f1d 100644 --- a/git-tutorial.js +++ b/git-tutorial.js @@ -472,6 +472,16 @@ function ___filesystem_to_table(fs, previous_filesystem) { for (var i = 0; i < entries.length; i++) { tbody.append(___format_entry(previous_filesystem, entries[i])); } + if (entries.length == 0) { + var tr_empty = document.createElement('tr'); + tbody.append(tr_empty); + + var td_empty = document.createElement('td'); + tr_empty.append(td_empty); + td_empty.setAttribute('colspan', '2'); + td_empty.classList.add('empty-filesystem'); + td_empty.innerText = "The filesystem is empty." + } return table; } function ___filesystem_to_string(fs, just_table, previous_filesystem) { @@ -514,21 +524,76 @@ function ___copyprintf_click(id) { elem.disabled = true; } } -var global_filesystem=false; +var ___script_log_header = '' + + 'var ___log = [];\n' + + 'var console = (function(real_console) {\n' + + ' return {\n' + + ' log: function() {\n' + + ' ___log[___log.length] = arguments;\n' + + ' real_console.log.apply(console, arguments);\n' + + ' },\n' + + ' assert: real_console.assert,\n' + + ' };\n' + + '})(window.console);\n' + + '\n'; + +function ___eval_result_to_string(filesystem, previous_filesystem, log) { + return '<pre>' + log.map(function(l) { return l.map(function (x) { return x.toString(); }).join(', '); }).join('\n') + '</pre>' + + ___filesystem_to_string(filesystem, false, previous_filesystem); +} function ___git_eval(current) { document.getElementById('hide-eval-' + current).style.display = ''; - var script = ''; + var script = ___script_log_header; for (i = 0; i <= current - 1; i++) { script += ___textarea_value(___global_editors[i]); } - script += "\n var ___previous_filesystem = {}; for (k in filesystem) { ___previous_filesystem[k] = filesystem[k]; }\n"; + script += '\n' + + 'var ___previous_filesystem = {};\n' + + 'for (k in filesystem) { ___previous_filesystem[k] = filesystem[k]; }\n' + + '___log = [];\n'; script += ___textarea_value(___global_editors[current]); - script += "\n document.getElementById('out' + current).innerHTML = ___filesystem_to_string(filesystem, false, ___previous_filesystem); filesystem;"; + script += '\n' + + '"End of the script";\n' + + '\n' + + '\n' + + 'document.getElementById("out" + current).innerHTML = ___eval_result_to_string(filesystem, ___previous_filesystem, ___log);\n' + + 'filesystem;\n'; try { - global_filesystem = eval(script); + document.getElementById('debug').innerText = script; + eval(script); } catch (e) { + // Stack traces usually include :line:column + var rx = /:([0-9][0-9]*):[0-9][0-9]*/g; + var linecol = rx.exec(''+e.stack); + var line = null; + if (linecol && linecol.length > 0) { + line=parseInt(linecol[1]); + } else { + // Some older versions of Firefox and probably some other browsers use just :line + var rx = /:([0-9][0-9]*)*/g; + var justline = rx.exec(''+e.stack); + if (justline && justline.length > 0) { + line=parseInt(justline[1], 10); + } + } + if (typeof(line) == 'number') { + var lines = script.split('\n'); + if (line < lines.length) { + var from = Math.max(0, line-2); + var to = Math.min(lines.length - 1, line+2+1); + var showline = '' + + 'Possible location of the error: near line ' + line + '\n' + + '\n' + + lines.slice(from, to).map(function(l, i) { return '' + (from + i) + ': ' + l; }).join('\n') + + '\n' + + '\n'; + } + } else { + var showline = 'Sorry, this tutorial could not pinpoint precisely\nthe location of the error.\n' + + 'The stacktrace below may contain more information.\n' + } var error = ___specialchars("" + e + "\n\n" + e.stack); - document.getElementById('out' + current).innerHTML = '<pre class="error">' + error + '</pre>'; + document.getElementById('out' + current).innerHTML = '<pre class="error">' + showline + error + '</pre>'; } } diff --git a/index.html b/index.html index d53b867..fd9fe40 100644 --- a/index.html +++ b/index.html @@ -516,7 +516,16 @@ directly to a commit hash like or can point to another symbolic reference, in which case the <code>.git/HEAD</code> file will contain e.g. <code>refs/heads/main</code>.</p> -<p>Branches are simple files stored in <code>.git/refs/heads/name-of-the-branch</code></p> +<p>Branches are simple files stored in <code>.git/refs/heads/name-of-the-branch</code> +and usually contain a hash like +<span id="example-reference-branch-hash">0123456789abcdef0123456789abcdef01234567</span>.</p> + +<p>Tags are identical to branches in terms of representation. It seems that the only difference +between tags and branches is the behaviour of <code>git checkout</code> and similar commands. +These commands, as explained in <a href="git-checkout">the section about <code>git checkout</code></a> below, +normally write <code>ref: refs/heads/name-of-branch</code> in <code>.git/HEAD</code> when +checking out a branch, but write the hash of the target commit when checking out a tag or +any other non-branch reference.</p> <div id="example-reference"></div> <script class="example"> @@ -545,6 +554,7 @@ will contain e.g. <code>refs/heads/main</code>.</p> var head = 'proj/.git/HEAD'; document.getElementById('example-reference-head-hash').innerText = initial_commit_hash; + document.getElementById('example-reference-branch-hash').innerText = initial_commit_hash; var previous_names = [ main, readme, src, proj, initial_commit ]; var names = [ main, readme, src, proj, initial_commit, main_branch, v1_0_tag, head ]; @@ -566,21 +576,77 @@ function trim_newline(s) { <section id="git-symbolic-ref"> <h1><code>git symbolic-ref</code></h1> +<p><code>git symbolic-ref</code> is a low-level command which reads +(and in the official GIT implementation also writes and updates) +symbolic references given a path relative to <code>.git/</code>. +For example, <code>git symbolic-ref HEAD</code> will read the +contents of the file <code>.git/HEAD</code>, and if that file starts +with <code>ref: </code>, the rest of the line will be returned.</p> + <textarea> function git_symbolic_ref(ref) { var ref_file = join_paths(current_directory, '.git/' + ref); if (exists(ref_file) && read(ref_file).startsWith('ref: ')) { - return trim_newline(read(ref_file)).substr('ref: '.length); + var result = trim_newline(read(ref_file)).substr('ref: '.length); + var recursive = git_symbolic_ref(result); + return recursive || result; } else { return false; } } </textarea> +<div class="trivia"> +<p>The official implementation of GIT follows references recursively + and returns the <code>path/to/file</code> of the last file of the + form <code>ref: path/to/file</code>. In the example below, + <code>git symbolic-ref HEAD</code> would + <ul> + <li>read the file <code>proj/.git/HEAD</code> which contains <code>ref: refs/heads/main</code>,</li> + <li>follow that indirection and read the file <code>proj/.git/refs/heads/main</code> which contains <code>ref: refs/heads/other</code></li> + <li>follow that indirection and read the file <code>proj/.git/refs/heads/other</code> which contains a hash</li> + <li>return the last file path that contained a <code>ref:</code>, i.e. return the string <code>refs/heads/other</code></li> + </ul> +<div id="example-recursive-ref"></div> +<script class="example"> + ___example('example-recursive-ref', function() { + var h2f = function(hash) { return 'proj/.git/objects/'+hash.substr(0,2)+'/'+hash.substr(2); } + var main = h2f(hash_object(true, 'blob', false, 'src/main.scm')); + var readme = h2f(hash_object(true, 'blob', false, 'README')); + var src = h2f(store_tree("src", ["main.scm"], [])); + var proj = h2f(paths_to_tree(["README", "src/main.scm"])); + + var initial_commit_hash = store_commit( + paths_to_tree(["README", "src/main.scm"]), + [], + {name:'Ada', email:'ada@...', date:new Date(1617120803000), timezoneMinutes: +60}, + {name:'Ada', email:'ada@...', date:new Date(1617120803000), timezoneMinutes: +60}, + 'Initial commit'); + var initial_commit = h2f(initial_commit_hash); + + write('proj/.git/refs/heads/main', 'ref: refs/heads/other\n'); + var main_branch = 'proj/.git/refs/heads/main'; + + git_branch('other', initial_commit_hash, true); + var other_branch = 'proj/.git/refs/heads/other'; + + git_init_head(); + var head = 'proj/.git/HEAD'; + + document.getElementById('example-reference-head-hash').innerText = initial_commit_hash; + document.getElementById('example-reference-branch-hash').innerText = initial_commit_hash; + + var previous_names = [ initial_commit ]; + var names = [ initial_commit, main_branch, other_branch, head ]; + return { filesystem: filesystem, names: names, previous_names: previous_names } + }); +</script> +</div> </section> <section id="git-rev-parse"> -<h1><code>git rev-parse</code></h1> +<h1><code>git rev-parse</code></h1> <textarea> +console.log('hello', 'world', 3); function git_rev_parse(ref) { var symbolic_ref_target = git_symbolic_ref(ref); if (symbolic_ref_target) { @@ -1052,11 +1118,12 @@ commands.</p> </section> <div id="toc"></div> +<pre id="debug"></pre> </article> <script> (function() { - var script = ''; + var script = ___script_log_header; var ta = document.getElementsByTagName('textarea'); for (var j = 0; j < ta.length; j++) { if (ta[j] == document.getElementById('playground-reset')) {